From dd4321d07e9ce69deac5caab2e05c8a68ceff7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Pakalns?= Date: Thu, 3 Oct 2024 10:39:24 +0300 Subject: [PATCH 1/5] egui 0.29 support --- Cargo.toml | 37 ++++++++++++++++-------------- examples/paint_callback.rs | 2 +- examples/render_egui_to_texture.rs | 2 +- examples/render_to_image_widget.rs | 2 +- examples/side_panel.rs | 2 +- examples/simple.rs | 2 +- examples/simple_multipass.rs | 34 +++++++++++++++++++++++++++ examples/two_windows.rs | 2 +- examples/ui.rs | 2 +- src/lib.rs | 35 +++++++++++++++++++++------- src/systems.rs | 19 +++++++++++---- 11 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 examples/simple_multipass.rs diff --git a/Cargo.toml b/Cargo.toml index 24729c9ed..824e51ec9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,9 @@ required-features = ["render"] name = "simple" required-features = ["render"] [[example]] +name = "simple_multipass" +required-features = ["render"] +[[example]] name = "two_windows" required-features = ["render"] [[example]] @@ -46,10 +49,10 @@ required-features = ["render"] [dependencies] bevy = { version = "0.14.0", default-features = false, features = [ - "bevy_asset", - "bevy_winit", + "bevy_asset", + "bevy_winit", ] } -egui = { version = "0.28", default-features = false, features = ["bytemuck"] } +egui = { version = "0.29", default-features = false, features = ["bytemuck"] } bytemuck = "1" webbrowser = { version = "1.0.1", optional = true } wgpu-types = "0.20" @@ -61,25 +64,25 @@ thread_local = { version = "1.1.0", optional = true } [dev-dependencies] version-sync = "0.9.4" bevy = { version = "0.14.0", default-features = false, features = [ - "x11", - "png", - "bevy_pbr", - "bevy_core_pipeline", - "tonemapping_luts", - "webgl2", + "x11", + "png", + "bevy_pbr", + "bevy_core_pipeline", + "tonemapping_luts", + "webgl2", ] } -egui = { version = "0.28", default-features = false, features = ["bytemuck"] } +egui = { version = "0.29", default-features = false, features = ["bytemuck"] } [target.'cfg(target_arch = "wasm32")'.dependencies] winit = "0.30" web-sys = { version = "0.3.63", features = [ - "Clipboard", - "ClipboardEvent", - "DataTransfer", - 'Document', - 'EventTarget', - "Window", - "Navigator", + "Clipboard", + "ClipboardEvent", + "DataTransfer", + 'Document', + 'EventTarget', + "Window", + "Navigator", ] } js-sys = "0.3.63" wasm-bindgen = "0.2.84" diff --git a/examples/paint_callback.rs b/examples/paint_callback.rs index 8bb2b217a..48b9f1a1d 100644 --- a/examples/paint_callback.rs +++ b/examples/paint_callback.rs @@ -20,7 +20,7 @@ use wgpu_types::{Extent3d, TextureUsages}; fn main() { App::new() - .add_plugins((DefaultPlugins, EguiPlugin, CustomPipelinePlugin)) + .add_plugins((DefaultPlugins, EguiPlugin::default(), CustomPipelinePlugin)) .add_systems(Startup, setup_worldspace) .add_systems( Update, diff --git a/examples/render_egui_to_texture.rs b/examples/render_egui_to_texture.rs index 953ee5844..e22fa350b 100644 --- a/examples/render_egui_to_texture.rs +++ b/examples/render_egui_to_texture.rs @@ -5,7 +5,7 @@ use wgpu_types::{Extent3d, TextureUsages}; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); - app.add_plugins(EguiPlugin); + app.add_plugins(EguiPlugin::default()); app.add_systems(Startup, setup_worldspace); app.add_systems(Update, (update_screenspace, update_worldspace)); app.run(); diff --git a/examples/render_to_image_widget.rs b/examples/render_to_image_widget.rs index c782b6899..709a0697a 100644 --- a/examples/render_to_image_widget.rs +++ b/examples/render_to_image_widget.rs @@ -13,7 +13,7 @@ use bevy_egui::{egui::Widget, EguiContexts, EguiPlugin, EguiUserTextures}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .add_systems(Startup, setup) .add_systems(Update, rotator_system) .add_systems(Update, render_to_image_example_system) diff --git a/examples/side_panel.rs b/examples/side_panel.rs index 14e9c56ef..f778dbd39 100644 --- a/examples/side_panel.rs +++ b/examples/side_panel.rs @@ -18,7 +18,7 @@ fn main() { App::new() .insert_resource(WinitSettings::desktop_app()) .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .init_resource::() .add_systems(Startup, setup_system) .add_systems(Update, ui_example_system) diff --git a/examples/simple.rs b/examples/simple.rs index 47d532f58..62d1f51f5 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -4,7 +4,7 @@ use bevy_egui::{EguiContexts, EguiPlugin}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) // Systems that create Egui widgets should be run during the `CoreSet::Update` set, // or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set). .add_systems(Update, ui_example_system) diff --git a/examples/simple_multipass.rs b/examples/simple_multipass.rs new file mode 100644 index 000000000..614728cd0 --- /dev/null +++ b/examples/simple_multipass.rs @@ -0,0 +1,34 @@ +use std::num::NonZero; + +use bevy::prelude::*; +use bevy_egui::{EguiContext, EguiFullOutput, EguiInput, EguiPlugin}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(EguiPlugin { manual_run: true }) + .add_systems(Update, ui_example_system) + .run(); +} + +fn ui_example_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput, &mut EguiFullOutput)>) { + for (mut ctx, mut egui_input, mut egui_full_output) in contexts.iter_mut() { + let ui = |ctx: &egui::Context| { + egui::Window::new("Hello").show(ctx, |ui| { + let passes = ui + .ctx() + .viewport(|viewport| viewport.output.num_completed_passes) + + 1; + ui.label(format!("Passes: {}", passes)); + ui.ctx().request_discard("Trying to reach max limit"); + }); + }; + + let ctx = ctx.get_mut(); + ctx.memory_mut(|memory| { + memory.options.max_passes = NonZero::new(5).unwrap(); + }); + + **egui_full_output = Some(ctx.run(egui_input.take(), ui)); + } +} diff --git a/examples/two_windows.rs b/examples/two_windows.rs index b072950f5..1a1b65330 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -13,7 +13,7 @@ struct Images { fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .init_resource::() .add_systems(Startup, load_assets_system) .add_systems(Startup, create_new_window_system) diff --git a/examples/ui.rs b/examples/ui.rs index b7622a0f2..e8a7057c9 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -32,7 +32,7 @@ fn main() { }), ..default() })) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .add_systems(Startup, configure_visuals_system) .add_systems(Startup, configure_ui_state_system) .add_systems(Update, update_ui_scale_factor_system) diff --git a/src/lib.rs b/src/lib.rs index 60c7c9cdc..9ca734208 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,13 @@ use bevy::{ use std::cell::{RefCell, RefMut}; /// Adds all Egui resources and render graph nodes. -pub struct EguiPlugin; +#[derive(Default)] +pub struct EguiPlugin { + /// Controls if egui must be run manually + /// + /// using `egui::context::Context` object `run` or `begin_pass` and `end_pass` function calls. + pub manual_run: bool, +} /// A resource for storing global UI settings. #[derive(Clone, Debug, Resource, Reflect)] @@ -183,6 +189,10 @@ impl Default for EguiSettings { #[derive(Component, Clone, Debug, Default, Deref, DerefMut)] pub struct EguiInput(pub egui::RawInput); +/// Is used to store Egui context output. +#[derive(Component, Clone, Default, Deref, DerefMut)] +pub struct EguiFullOutput(pub Option); + /// A resource for accessing clipboard. /// /// The resource is available only if `manage_clipboard` feature is enabled. @@ -714,12 +724,17 @@ impl Plugin for EguiPlugin { .after(InputSystem) .after(EguiSet::InitContexts), ); - app.add_systems( - PreUpdate, - begin_frame_system - .in_set(EguiSet::BeginFrame) - .after(EguiSet::ProcessInput), - ); + + if !self.manual_run { + app.add_systems( + PreUpdate, + begin_pass_system + .in_set(EguiSet::BeginFrame) + .after(EguiSet::ProcessInput), + ); + app.add_systems(PostUpdate, end_pass_system.before(EguiSet::ProcessOutput)); + } + app.add_systems( PostUpdate, process_output_system.in_set(EguiSet::ProcessOutput), @@ -789,6 +804,8 @@ pub struct EguiContextQuery { pub ctx: &'static mut EguiContext, /// Encapsulates [`egui::RawInput`]. pub egui_input: &'static mut EguiInput, + /// Encapsulates [`egui::FullOutput`]. + pub egui_full_output: &'static mut EguiFullOutput, /// Egui shapes and textures delta. pub render_output: &'static mut EguiRenderOutput, /// Encapsulates [`egui::PlatformOutput`]. @@ -826,6 +843,7 @@ pub fn setup_new_windows_system( EguiContext::default(), EguiRenderOutput::default(), EguiInput::default(), + EguiFullOutput::default(), EguiOutput::default(), RenderTargetSize::default(), )); @@ -848,6 +866,7 @@ pub fn setup_render_to_texture_handles_system( EguiContext::default(), EguiRenderOutput::default(), EguiInput::default(), + EguiFullOutput::default(), EguiOutput::default(), RenderTargetSize::default(), )); @@ -978,7 +997,7 @@ mod tests { .build() .disable::(), ) - .add_plugins(EguiPlugin) + .add_plugins(EguiPlugin::default()) .update(); } } diff --git a/src/systems.rs b/src/systems.rs index 07e1d85c0..8ca0fbe7b 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,7 +1,8 @@ #[cfg(feature = "render")] use crate::EguiRenderToTextureHandle; use crate::{ - EguiContext, EguiContextQuery, EguiContextQueryItem, EguiInput, EguiSettings, RenderTargetSize, + EguiContext, EguiContextQuery, EguiContextQueryItem, EguiFullOutput, EguiInput, EguiSettings, + RenderTargetSize, }; #[cfg(feature = "render")] @@ -491,9 +492,16 @@ pub fn update_contexts_system( } /// Marks frame start for Egui. -pub fn begin_frame_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput)>) { +pub fn begin_pass_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput)>) { for (mut ctx, mut egui_input) in contexts.iter_mut() { - ctx.get_mut().begin_frame(egui_input.take()); + ctx.get_mut().begin_pass(egui_input.take()); + } +} + +/// Marks frame end for Egui. +pub fn end_pass_system(mut contexts: Query<(&mut EguiContext, &mut EguiFullOutput)>) { + for (mut ctx, mut full_output) in contexts.iter_mut() { + **full_output = Some(ctx.get_mut().end_pass()); } } @@ -513,7 +521,10 @@ pub fn process_output_system( for mut context in contexts.iter_mut() { let ctx = context.ctx.get_mut(); - let full_output = ctx.end_frame(); + let Some(full_output) = context.egui_full_output.0.take() else { + log::error!("bevy_egui frame output has not been prepared!"); + continue; + }; let egui::FullOutput { platform_output, shapes, From 0961d510e2e1b111f61fc2871ad35d1f06804a59 Mon Sep 17 00:00:00 2001 From: mvlabat Date: Fri, 4 Oct 2024 13:40:55 +0300 Subject: [PATCH 2/5] Configure manual run via EguiSettings, make settings a component --- CHANGELOG.md | 17 +++++++-- examples/paint_callback.rs | 2 +- examples/render_egui_to_texture.rs | 2 +- examples/render_to_image_widget.rs | 2 +- examples/side_panel.rs | 2 +- examples/simple.rs | 2 +- examples/simple_multipass.rs | 12 +++++-- examples/two_windows.rs | 2 +- examples/ui.rs | 9 +++-- src/egui_node.rs | 13 ++++--- src/egui_render_to_texture_node.rs | 12 +++---- src/lib.rs | 57 +++++++++++++++--------------- src/render_systems.rs | 9 ++--- src/systems.rs | 41 ++++++++++----------- 14 files changed, 98 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d1a99e9..63e53808a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,23 @@ 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 + +### Changed + +- Update Egui to 0.29 ([#313](https://github.com/mvlabat/bevy_egui/pull/313) by @PPakalns). + +### Additional notes on breaking changes + +- `EguiSettings` is now a component. +- `EguiSet::BeginFrame` has been renamed to `EguiSet::BeginPass`. + ## [0.29.0] - 18-Aug-2024 ### Added -- Initial worldspace UI support ([#304](https://github.com/mvlabat/bevy_egui/pull/304) by @TheButlah, @Schmarni-Dev) -- Paint callback support ([#303](https://github.com/mvlabat/bevy_egui/pull/303) by @PPakalns) +- Initial worldspace UI support ([#304](https://github.com/mvlabat/bevy_egui/pull/304) by @TheButlah, @Schmarni-Dev). +- Paint callback support ([#303](https://github.com/mvlabat/bevy_egui/pull/303) by @PPakalns). ### Changed @@ -75,7 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update Bevy to 0.13 ([#236](https://github.com/mvlabat/bevy_egui/pull/236) by @eri). -- Update Egui to 0.26 +- Update Egui to 0.26. ### Fixed diff --git a/examples/paint_callback.rs b/examples/paint_callback.rs index 48b9f1a1d..8bb2b217a 100644 --- a/examples/paint_callback.rs +++ b/examples/paint_callback.rs @@ -20,7 +20,7 @@ use wgpu_types::{Extent3d, TextureUsages}; fn main() { App::new() - .add_plugins((DefaultPlugins, EguiPlugin::default(), CustomPipelinePlugin)) + .add_plugins((DefaultPlugins, EguiPlugin, CustomPipelinePlugin)) .add_systems(Startup, setup_worldspace) .add_systems( Update, diff --git a/examples/render_egui_to_texture.rs b/examples/render_egui_to_texture.rs index e22fa350b..953ee5844 100644 --- a/examples/render_egui_to_texture.rs +++ b/examples/render_egui_to_texture.rs @@ -5,7 +5,7 @@ use wgpu_types::{Extent3d, TextureUsages}; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); - app.add_plugins(EguiPlugin::default()); + app.add_plugins(EguiPlugin); app.add_systems(Startup, setup_worldspace); app.add_systems(Update, (update_screenspace, update_worldspace)); app.run(); diff --git a/examples/render_to_image_widget.rs b/examples/render_to_image_widget.rs index 709a0697a..c782b6899 100644 --- a/examples/render_to_image_widget.rs +++ b/examples/render_to_image_widget.rs @@ -13,7 +13,7 @@ use bevy_egui::{egui::Widget, EguiContexts, EguiPlugin, EguiUserTextures}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin::default()) + .add_plugins(EguiPlugin) .add_systems(Startup, setup) .add_systems(Update, rotator_system) .add_systems(Update, render_to_image_example_system) diff --git a/examples/side_panel.rs b/examples/side_panel.rs index f778dbd39..14e9c56ef 100644 --- a/examples/side_panel.rs +++ b/examples/side_panel.rs @@ -18,7 +18,7 @@ fn main() { App::new() .insert_resource(WinitSettings::desktop_app()) .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin::default()) + .add_plugins(EguiPlugin) .init_resource::() .add_systems(Startup, setup_system) .add_systems(Update, ui_example_system) diff --git a/examples/simple.rs b/examples/simple.rs index 62d1f51f5..47d532f58 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -4,7 +4,7 @@ use bevy_egui::{EguiContexts, EguiPlugin}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin::default()) + .add_plugins(EguiPlugin) // Systems that create Egui widgets should be run during the `CoreSet::Update` set, // or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set). .add_systems(Update, ui_example_system) diff --git a/examples/simple_multipass.rs b/examples/simple_multipass.rs index 614728cd0..dd321115c 100644 --- a/examples/simple_multipass.rs +++ b/examples/simple_multipass.rs @@ -1,16 +1,24 @@ use std::num::NonZero; use bevy::prelude::*; -use bevy_egui::{EguiContext, EguiFullOutput, EguiInput, EguiPlugin}; +use bevy_egui::{EguiContext, EguiFullOutput, EguiInput, EguiPlugin, EguiSettings, EguiStartupSet}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin { manual_run: true }) + .add_plugins(EguiPlugin) + .add_systems( + PreStartup, + configure_context.after(EguiStartupSet::InitContexts), + ) .add_systems(Update, ui_example_system) .run(); } +fn configure_context(mut egui_settings: Query<&mut EguiSettings>) { + egui_settings.single_mut().run_manually = true; +} + fn ui_example_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput, &mut EguiFullOutput)>) { for (mut ctx, mut egui_input, mut egui_full_output) in contexts.iter_mut() { let ui = |ctx: &egui::Context| { diff --git a/examples/two_windows.rs b/examples/two_windows.rs index 1a1b65330..b072950f5 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -13,7 +13,7 @@ struct Images { fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) - .add_plugins(EguiPlugin::default()) + .add_plugins(EguiPlugin) .init_resource::() .add_systems(Startup, load_assets_system) .add_systems(Startup, create_new_window_system) diff --git a/examples/ui.rs b/examples/ui.rs index e8a7057c9..b518986b7 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -1,4 +1,4 @@ -use bevy::{prelude::*, window::PrimaryWindow}; +use bevy::prelude::*; use bevy_egui::{EguiContexts, EguiPlugin, EguiSettings}; struct Images { @@ -32,7 +32,7 @@ fn main() { }), ..default() })) - .add_plugins(EguiPlugin::default()) + .add_plugins(EguiPlugin) .add_systems(Startup, configure_visuals_system) .add_systems(Startup, configure_ui_state_system) .add_systems(Update, update_ui_scale_factor_system) @@ -63,13 +63,12 @@ fn configure_ui_state_system(mut ui_state: ResMut) { fn update_ui_scale_factor_system( keyboard_input: Res>, mut toggle_scale_factor: Local>, - mut egui_settings: ResMut, - windows: Query<&Window, With>, + mut contexts: Query<(&mut EguiSettings, &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 Ok(window) = windows.get_single() { + if let Ok((mut egui_settings, window)) = contexts.get_single_mut() { let scale_factor = if toggle_scale_factor.unwrap() { 1.0 } else { diff --git a/src/egui_node.rs b/src/egui_node.rs index 0966e35e5..d0a8447d9 100644 --- a/src/egui_node.rs +++ b/src/egui_node.rs @@ -232,20 +232,17 @@ impl Node for EguiNode { return; }; - let mut render_target_size = world.query::<(&RenderTargetSize, &mut EguiRenderOutput)>(); + let mut render_target_query = + world.query::<(&EguiSettings, &RenderTargetSize, &mut EguiRenderOutput)>(); - let Ok((window_size, mut render_output)) = - render_target_size.get_mut(world, self.window_entity) + let Ok((egui_settings, window_size, mut render_output)) = + render_target_query.get_mut(world, self.window_entity) else { return; }; let window_size = *window_size; let paint_jobs = std::mem::take(&mut render_output.paint_jobs); - let egui_settings = &world.get_resource::().unwrap(); - - let render_device = world.get_resource::().unwrap(); - self.pixels_per_point = window_size.scale_factor * egui_settings.scale_factor; if window_size.physical_width == 0.0 || window_size.physical_height == 0.0 { return; @@ -258,6 +255,8 @@ impl Node for EguiNode { self.index_data.clear(); self.postponed_updates.clear(); + let render_device = world.get_resource::().unwrap(); + for egui::epaint::ClippedPrimitive { clip_rect, primitive, diff --git a/src/egui_render_to_texture_node.rs b/src/egui_render_to_texture_node.rs index 594c1da60..3d0a3f8f4 100644 --- a/src/egui_render_to_texture_node.rs +++ b/src/egui_render_to_texture_node.rs @@ -79,9 +79,10 @@ impl Node for EguiRenderToTextureNode { return; }; - let mut render_target_sizes = world.query::<(&RenderTargetSize, &mut EguiRenderOutput)>(); - let Ok((render_target_size, mut render_output)) = - render_target_sizes.get_mut(world, self.render_to_texture_target) + let mut render_target_query = + world.query::<(&EguiSettings, &RenderTargetSize, &mut EguiRenderOutput)>(); + let Ok((egui_settings, render_target_size, mut render_output)) = + render_target_query.get_mut(world, self.render_to_texture_target) else { return; }; @@ -89,15 +90,12 @@ impl Node for EguiRenderToTextureNode { let render_target_size = *render_target_size; let paint_jobs = std::mem::take(&mut render_output.paint_jobs); - let egui_settings = &world.get_resource::().unwrap(); - - let render_device = world.get_resource::().unwrap(); - self.pixels_per_point = render_target_size.scale_factor * egui_settings.scale_factor; if render_target_size.physical_width == 0.0 || render_target_size.physical_height == 0.0 { return; } + let render_device = world.get_resource::().unwrap(); let mut index_offset = 0; self.draw_commands.clear(); diff --git a/src/lib.rs b/src/lib.rs index 9ca734208..923d7beb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,18 +130,16 @@ use bevy::{ use std::cell::{RefCell, RefMut}; /// Adds all Egui resources and render graph nodes. -#[derive(Default)] -pub struct EguiPlugin { - /// Controls if egui must be run manually - /// - /// using `egui::context::Context` object `run` or `begin_pass` and `end_pass` function calls. - pub manual_run: bool, -} +pub struct EguiPlugin; -/// A resource for storing global UI settings. -#[derive(Clone, Debug, Resource, Reflect)] -#[cfg_attr(feature = "render", derive(ExtractResource))] +/// A component for storing Egui context settings. +#[derive(Clone, Debug, Component, Reflect)] +#[cfg_attr(feature = "render", derive(ExtractComponent))] pub struct EguiSettings { + /// Controls if Egui is run manually. + /// + /// If set to `true`, a user is expected to call [`egui::Context::run`] or [`egui::Context::begin_pass`] and [`egui::Context::end_pass`] manually. + pub run_manually: bool, /// Global scale factor for Egui widgets (`1.0` by default). /// /// This setting can be used to force the UI to render in physical pixels regardless of DPI as follows: @@ -149,14 +147,14 @@ pub struct EguiSettings { /// use bevy::{prelude::*, window::PrimaryWindow}; /// use bevy_egui::EguiSettings; /// - /// fn update_ui_scale_factor(mut egui_settings: ResMut, windows: Query<&Window, With>) { - /// if let Ok(window) = windows.get_single() { + /// fn update_ui_scale_factor(mut windows: Query<(&mut EguiSettings, &Window), With>) { + /// if let Ok((mut egui_settings, window)) = windows.get_single_mut() { /// egui_settings.scale_factor = 1.0 / window.scale_factor(); /// } /// } /// ``` pub scale_factor: f32, - /// Will be used as a default value for hyperlink [target](https://www.w3schools.com/tags/att_a_target.asp) hints. + /// Is used as a default value for hyperlink [target](https://www.w3schools.com/tags/att_a_target.asp) hints. /// If not specified, `_self` will be used. Only matters in a web browser. #[cfg(feature = "open_url")] pub default_open_url_target: Option, @@ -176,6 +174,7 @@ impl PartialEq for EguiSettings { impl Default for EguiSettings { fn default() -> Self { Self { + run_manually: false, scale_factor: 1.0, #[cfg(feature = "open_url")] default_open_url_target: None, @@ -661,7 +660,6 @@ impl Plugin for EguiPlugin { app.register_type::(); let world = app.world_mut(); - world.init_resource::(); #[cfg(feature = "render")] world.init_resource::(); #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))] @@ -679,10 +677,10 @@ impl Plugin for EguiPlugin { #[cfg(feature = "render")] app.add_plugins(ExtractResourcePlugin::::default()); #[cfg(feature = "render")] - app.add_plugins(ExtractResourcePlugin::::default()); - #[cfg(feature = "render")] app.add_plugins(ExtractComponentPlugin::::default()); #[cfg(feature = "render")] + app.add_plugins(ExtractComponentPlugin::::default()); + #[cfg(feature = "render")] app.add_plugins(ExtractComponentPlugin::::default()); #[cfg(feature = "render")] app.add_plugins(ExtractComponentPlugin::::default()); @@ -706,6 +704,7 @@ impl Plugin for EguiPlugin { .chain() .in_set(EguiStartupSet::InitContexts), ); + app.add_systems( PreUpdate, ( @@ -724,21 +723,19 @@ impl Plugin for EguiPlugin { .after(InputSystem) .after(EguiSet::InitContexts), ); + app.add_systems( + PreUpdate, + begin_pass_system + .in_set(EguiSet::BeginFrame) + .after(EguiSet::ProcessInput), + ); - if !self.manual_run { - app.add_systems( - PreUpdate, - begin_pass_system - .in_set(EguiSet::BeginFrame) - .after(EguiSet::ProcessInput), - ); - app.add_systems(PostUpdate, end_pass_system.before(EguiSet::ProcessOutput)); - } - + app.add_systems(PostUpdate, end_pass_system.before(EguiSet::ProcessOutput)); app.add_systems( PostUpdate, process_output_system.in_set(EguiSet::ProcessOutput), ); + #[cfg(feature = "render")] app.add_systems( PostUpdate, @@ -800,8 +797,10 @@ impl Plugin for EguiPlugin { pub struct EguiContextQuery { /// Window entity. pub render_target: Entity, - /// Egui context associated with the window. + /// Egui context associated with the render target. pub ctx: &'static mut EguiContext, + /// Settings associated with the context. + pub egui_settings: &'static mut EguiSettings, /// Encapsulates [`egui::RawInput`]. pub egui_input: &'static mut EguiInput, /// Encapsulates [`egui::FullOutput`]. @@ -841,6 +840,7 @@ pub fn setup_new_windows_system( for window in new_windows.iter() { commands.entity(window).insert(( EguiContext::default(), + EguiSettings::default(), EguiRenderOutput::default(), EguiInput::default(), EguiFullOutput::default(), @@ -864,6 +864,7 @@ pub fn setup_render_to_texture_handles_system( for render_to_texture_target in new_render_to_texture_targets.iter() { commands.entity(render_to_texture_target).insert(( EguiContext::default(), + EguiSettings::default(), EguiRenderOutput::default(), EguiInput::default(), EguiFullOutput::default(), @@ -997,7 +998,7 @@ mod tests { .build() .disable::(), ) - .add_plugins(EguiPlugin::default()) + .add_plugins(EguiPlugin) .update(); } } diff --git a/src/render_systems.rs b/src/render_systems.rs index bae941bc5..cf9d6a99c 100644 --- a/src/render_systems.rs +++ b/src/render_systems.rs @@ -160,25 +160,22 @@ impl EguiTransform { /// Prepares Egui transforms. pub fn prepare_egui_transforms_system( mut egui_transforms: ResMut, - render_target_sizes: Query<(Entity, &RenderTargetSize)>, - egui_settings: Res, - + render_targets: Query<(Entity, &EguiSettings, &RenderTargetSize)>, render_device: Res, render_queue: Res, - egui_pipeline: Res, ) { egui_transforms.buffer.clear(); egui_transforms.offsets.clear(); - for (window, size) in render_target_sizes.iter() { + for (render_target, egui_settings, size) in render_targets.iter() { let offset = egui_transforms .buffer .push(&EguiTransform::from_render_target_size( *size, egui_settings.scale_factor, )); - egui_transforms.offsets.insert(window, offset); + egui_transforms.offsets.insert(render_target, offset); } egui_transforms diff --git a/src/systems.rs b/src/systems.rs index 8ca0fbe7b..957503fb9 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -107,7 +107,6 @@ pub fn process_input_system( mut input_events: InputEvents, mut input_resources: InputResources, mut context_params: ContextSystemParams, - egui_settings: Res, time: Res>, ) { // Test whether it's macOS or OS X. @@ -181,7 +180,7 @@ pub fn process_input_system( continue; }; - let scale_factor = egui_settings.scale_factor; + let scale_factor = window_context.egui_settings.scale_factor; let (x, y): (f32, f32) = (event.position / scale_factor).into(); let mouse_position = egui::pos2(x, y); window_context.ctx.mouse_position = mouse_position; @@ -337,7 +336,7 @@ pub fn process_input_system( }; let touch_id = egui::TouchId::from(event.id); - let scale_factor = egui_settings.scale_factor; + let scale_factor = window_context.egui_settings.scale_factor; let touch_position: (f32, f32) = (event.position / scale_factor).into(); // Emit touch event @@ -439,7 +438,6 @@ pub fn process_input_system( /// Initialises Egui contexts (for multiple windows). pub fn update_contexts_system( mut context_params: ContextSystemParams, - egui_settings: Res, #[cfg(feature = "render")] images: Res>, ) { for mut context in context_params.contexts.iter_mut() { @@ -468,10 +466,10 @@ pub fn update_contexts_system( }; let width = new_render_target_size.physical_width / new_render_target_size.scale_factor - / egui_settings.scale_factor; + / context.egui_settings.scale_factor; let height = new_render_target_size.physical_height / new_render_target_size.scale_factor - / egui_settings.scale_factor; + / context.egui_settings.scale_factor; if width < 1.0 || height < 1.0 { continue; @@ -482,34 +480,36 @@ pub fn update_contexts_system( egui::pos2(width, height), )); - context - .ctx - .get_mut() - .set_pixels_per_point(new_render_target_size.scale_factor * egui_settings.scale_factor); + context.ctx.get_mut().set_pixels_per_point( + new_render_target_size.scale_factor * context.egui_settings.scale_factor, + ); *context.render_target_size = new_render_target_size; } } /// Marks frame start for Egui. -pub fn begin_pass_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput)>) { - for (mut ctx, mut egui_input) in contexts.iter_mut() { - ctx.get_mut().begin_pass(egui_input.take()); +pub fn begin_pass_system(mut contexts: Query<(&mut EguiContext, &EguiSettings, &mut EguiInput)>) { + for (mut ctx, egui_settings, mut egui_input) in contexts.iter_mut() { + if !egui_settings.run_manually { + ctx.get_mut().begin_pass(egui_input.take()); + } } } /// Marks frame end for Egui. -pub fn end_pass_system(mut contexts: Query<(&mut EguiContext, &mut EguiFullOutput)>) { - for (mut ctx, mut full_output) in contexts.iter_mut() { - **full_output = Some(ctx.get_mut().end_pass()); +pub fn end_pass_system( + mut contexts: Query<(&mut EguiContext, &EguiSettings, &mut EguiFullOutput)>, +) { + for (mut ctx, egui_settings, mut full_output) in contexts.iter_mut() { + if !egui_settings.run_manually { + **full_output = Some(ctx.get_mut().end_pass()); + } } } /// Reads Egui output. pub fn process_output_system( - #[cfg_attr(not(feature = "open_url"), allow(unused_variables))] egui_settings: Res< - EguiSettings, - >, mut contexts: Query, #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))] mut egui_clipboard: bevy::ecs::system::ResMut, @@ -589,7 +589,8 @@ pub fn process_output_system( let target = if new_tab { "_blank" } else { - egui_settings + context + .egui_settings .default_open_url_target .as_deref() .unwrap_or("_self") From 831c11b81ea4714b8fdd9589ef1c67f22c6d2602 Mon Sep 17 00:00:00 2001 From: mvlabat Date: Fri, 4 Oct 2024 13:54:16 +0300 Subject: [PATCH 3/5] Fix clippy warnings for no-default-features --- src/lib.rs | 83 ++++++++++++++++++++++++------------------------------ 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 923d7beb7..c5d69a2e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,8 +117,8 @@ use bevy::{ }, input::InputSystem, prelude::{ - Added, Commands, Component, Deref, DerefMut, Entity, IntoSystemConfigs, Query, Resource, - SystemSet, With, Without, + Added, Commands, Component, Deref, DerefMut, Entity, IntoSystemConfigs, Query, SystemSet, + With, Without, }, reflect::Reflect, window::{PrimaryWindow, Window}, @@ -196,7 +196,7 @@ pub struct EguiFullOutput(pub Option); /// /// The resource is available only if `manage_clipboard` feature is enabled. #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))] -#[derive(Default, Resource)] +#[derive(Default, bevy::ecs::system::Resource)] pub struct EguiClipboard { #[cfg(not(target_arch = "wasm32"))] clipboard: thread_local::ThreadLocal>>, @@ -549,7 +549,7 @@ impl<'w, 's> EguiContexts<'w, 's> { pub struct EguiRenderToTextureHandle(pub Handle); /// A resource for storing `bevy_egui` user textures. -#[derive(Clone, Resource, Default, ExtractResource)] +#[derive(Clone, bevy::ecs::system::Resource, Default, ExtractResource)] #[cfg(feature = "render")] pub struct EguiUserTextures { textures: HashMap, u64>, @@ -659,40 +659,32 @@ impl Plugin for EguiPlugin { fn build(&self, app: &mut App) { app.register_type::(); - let world = app.world_mut(); #[cfg(feature = "render")] - world.init_resource::(); + { + app.init_resource::(); + app.init_resource::(); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + } + #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))] - world.init_resource::(); - #[cfg(all( - feature = "manage_clipboard", - target_arch = "wasm32", - web_sys_unstable_apis - ))] - world.init_non_send_resource::(); - #[cfg(feature = "render")] - world.init_resource::(); - #[cfg(feature = "render")] - app.add_plugins(ExtractResourcePlugin::::default()); - #[cfg(feature = "render")] - app.add_plugins(ExtractResourcePlugin::::default()); - #[cfg(feature = "render")] - app.add_plugins(ExtractComponentPlugin::::default()); - #[cfg(feature = "render")] - app.add_plugins(ExtractComponentPlugin::::default()); - #[cfg(feature = "render")] - app.add_plugins(ExtractComponentPlugin::::default()); - #[cfg(feature = "render")] - app.add_plugins(ExtractComponentPlugin::::default()); - #[cfg(feature = "render")] - app.add_plugins(ExtractComponentPlugin::::default()); + app.init_resource::(); #[cfg(all( feature = "manage_clipboard", target_arch = "wasm32", web_sys_unstable_apis ))] - app.add_systems(PreStartup, web_clipboard::startup_setup_web_events); + { + app.init_non_send_resource::(); + app.add_systems(PreStartup, web_clipboard::startup_setup_web_events); + } + app.add_systems( PreStartup, ( @@ -740,21 +732,20 @@ impl Plugin for EguiPlugin { app.add_systems( PostUpdate, update_egui_textures_system.after(EguiSet::ProcessOutput), - ); - #[cfg(feature = "render")] - app.add_systems(Last, free_egui_textures_system) - .add_systems( - Render, - render_systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare), - ) - .add_systems( - Render, - render_systems::queue_bind_groups_system.in_set(RenderSet::Queue), - ) - .add_systems( - Render, - render_systems::queue_pipelines_system.in_set(RenderSet::Queue), - ); + ) + .add_systems( + Render, + render_systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare), + ) + .add_systems( + Render, + render_systems::queue_bind_groups_system.in_set(RenderSet::Queue), + ) + .add_systems( + Render, + render_systems::queue_pipelines_system.in_set(RenderSet::Queue), + ) + .add_systems(Last, free_egui_textures_system); #[cfg(feature = "render")] load_internal_asset!(app, EGUI_SHADER_HANDLE, "egui.wgsl", Shader::from_wgsl); @@ -820,7 +811,7 @@ pub struct EguiContextQuery { /// Contains textures allocated and painted by Egui. #[cfg(feature = "render")] -#[derive(Resource, Deref, DerefMut, Default)] +#[derive(bevy::ecs::system::Resource, Deref, DerefMut, Default)] pub struct EguiManagedTextures(pub HashMap<(Entity, u64), EguiManagedTexture>); /// Represents a texture allocated and painted by Egui. From b44c7af2452ea98a38e71ba8b29e973b8006c6cc Mon Sep 17 00:00:00 2001 From: mvlabat Date: Fri, 4 Oct 2024 13:56:19 +0300 Subject: [PATCH 4/5] Make queries of the simple_multipass example consitent --- examples/simple_multipass.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/simple_multipass.rs b/examples/simple_multipass.rs index dd321115c..fc611f92b 100644 --- a/examples/simple_multipass.rs +++ b/examples/simple_multipass.rs @@ -20,23 +20,23 @@ fn configure_context(mut egui_settings: Query<&mut EguiSettings>) { } fn ui_example_system(mut contexts: Query<(&mut EguiContext, &mut EguiInput, &mut EguiFullOutput)>) { - for (mut ctx, mut egui_input, mut egui_full_output) in contexts.iter_mut() { - let ui = |ctx: &egui::Context| { - egui::Window::new("Hello").show(ctx, |ui| { - let passes = ui - .ctx() - .viewport(|viewport| viewport.output.num_completed_passes) - + 1; - ui.label(format!("Passes: {}", passes)); - ui.ctx().request_discard("Trying to reach max limit"); - }); - }; + let (mut ctx, mut egui_input, mut egui_full_output) = contexts.single_mut(); - let ctx = ctx.get_mut(); - ctx.memory_mut(|memory| { - memory.options.max_passes = NonZero::new(5).unwrap(); + let ui = |ctx: &egui::Context| { + egui::Window::new("Hello").show(ctx, |ui| { + let passes = ui + .ctx() + .viewport(|viewport| viewport.output.num_completed_passes) + + 1; + ui.label(format!("Passes: {}", passes)); + ui.ctx().request_discard("Trying to reach max limit"); }); + }; - **egui_full_output = Some(ctx.run(egui_input.take(), ui)); - } + let ctx = ctx.get_mut(); + ctx.memory_mut(|memory| { + memory.options.max_passes = NonZero::new(5).unwrap(); + }); + + **egui_full_output = Some(ctx.run(egui_input.take(), ui)); } From cb4ec69f28d82460a8c6328a5b653598adc3c5f2 Mon Sep 17 00:00:00 2001 From: mvlabat Date: Fri, 4 Oct 2024 14:03:16 +0300 Subject: [PATCH 5/5] Rename BeginFrame to BeginPass --- README.md | 2 +- examples/simple.rs | 2 +- src/lib.rs | 10 +++++----- src/systems.rs | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ecde33c56..86a52c59a 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugins(EguiPlugin) // Systems that create Egui widgets should be run during the `CoreSet::Update` set, - // or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set). + // or after the `EguiSet::BeginPass` system (which belongs to the `CoreSet::PreUpdate` set). .add_systems(Update, ui_example_system) .run(); } diff --git a/examples/simple.rs b/examples/simple.rs index 47d532f58..8a518a4ce 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -6,7 +6,7 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugins(EguiPlugin) // Systems that create Egui widgets should be run during the `CoreSet::Update` set, - // or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set). + // or after the `EguiSet::BeginPass` system (which belongs to the `CoreSet::PreUpdate` set). .add_systems(Update, ui_example_system) .run(); } diff --git a/src/lib.rs b/src/lib.rs index c5d69a2e0..d1745605f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ //! .add_plugins(DefaultPlugins) //! .add_plugins(EguiPlugin) //! // Systems that create Egui widgets should be run during the `CoreSet::Update` set, -//! // or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set). +//! // or after the `EguiSet::BeginPass` system (which belongs to the `CoreSet::PreUpdate` set). //! .add_systems(Update, ui_example_system) //! .run(); //! } @@ -647,10 +647,10 @@ pub enum EguiSet { /// /// To modify the input, you can hook your system like this: /// - /// `system.after(EguiSet::ProcessInput).before(EguiSet::BeginFrame)`. + /// `system.after(EguiSet::ProcessInput).before(EguiSet::BeginPass)`. ProcessInput, - /// Begins the `egui` frame. - BeginFrame, + /// Begins the `egui` pass. + BeginPass, /// Processes the [`EguiOutput`] resource. ProcessOutput, } @@ -718,7 +718,7 @@ impl Plugin for EguiPlugin { app.add_systems( PreUpdate, begin_pass_system - .in_set(EguiSet::BeginFrame) + .in_set(EguiSet::BeginPass) .after(EguiSet::ProcessInput), ); diff --git a/src/systems.rs b/src/systems.rs index 957503fb9..d9b54c052 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -488,7 +488,7 @@ pub fn update_contexts_system( } } -/// Marks frame start for Egui. +/// Marks a pass start for Egui. pub fn begin_pass_system(mut contexts: Query<(&mut EguiContext, &EguiSettings, &mut EguiInput)>) { for (mut ctx, egui_settings, mut egui_input) in contexts.iter_mut() { if !egui_settings.run_manually { @@ -497,7 +497,7 @@ pub fn begin_pass_system(mut contexts: Query<(&mut EguiContext, &EguiSettings, & } } -/// Marks frame end for Egui. +/// Marks a pass end for Egui. pub fn end_pass_system( mut contexts: Query<(&mut EguiContext, &EguiSettings, &mut EguiFullOutput)>, ) { @@ -522,7 +522,7 @@ pub fn process_output_system( for mut context in contexts.iter_mut() { let ctx = context.ctx.get_mut(); let Some(full_output) = context.egui_full_output.0.take() else { - log::error!("bevy_egui frame output has not been prepared!"); + log::error!("bevy_egui pass output has not been prepared (if EguiSettings::run_manually is set to true, make sure to call egui::Context::run or egui::Context::begin_pass and egui::Context::end_pass)"); continue; }; let egui::FullOutput {