bevy_egui/
lib.rs

1#![warn(missing_docs)]
2#![allow(clippy::type_complexity)]
3
4//! This crate provides an [Egui](https://github.com/emilk/egui) integration for the [Bevy](https://github.com/bevyengine/bevy) game engine.
5//!
6//! **Trying out:**
7//!
8//! A basic WASM example is live at [vladbat00.github.io/bevy_egui/ui](https://vladbat00.github.io/bevy_egui/ui/).
9//!
10//! **Features:**
11//! - Desktop and web platforms support
12//! - Clipboard
13//! - Opening URLs
14//! - Multiple windows support (see [./examples/two_windows.rs](https://github.com/vladbat00/bevy_egui/blob/v0.29.0/examples/two_windows.rs))
15//! - Paint callback support (see [./examples/paint_callback.rs](https://github.com/vladbat00/bevy_egui/blob/v0.29.0/examples/paint_callback.rs))
16//! - Mobile web virtual keyboard (still rough around the edges and only works without `prevent_default_event_handling` set to `false` in the `WindowPlugin` settings)
17//!
18//! ## Dependencies
19//!
20//! On Linux, this crate requires certain parts of [XCB](https://xcb.freedesktop.org/) to be installed on your system. On Debian-based systems, these can be installed with the following command:
21//!
22//! ```bash
23//! sudo apt install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
24//! ```
25//!
26//! ## Usage
27//!
28//! Here's a minimal usage example:
29//!
30//! ```no_run,rust
31//! use bevy::prelude::*;
32//! use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiPrimaryContextPass};
33//!
34//! fn main() {
35//!     App::new()
36//!         .add_plugins(DefaultPlugins)
37//!         .add_plugins(EguiPlugin::default())
38//!         .add_systems(EguiPrimaryContextPass, ui_example_system)
39//!         .run();
40//! }
41//!
42//! fn ui_example_system(mut contexts: EguiContexts) -> Result {
43//!     egui::Window::new("Hello").show(contexts.ctx_mut()?, |ui| {
44//!         ui.label("world");
45//!     });
46//!     Ok(())
47//! }
48//! ```
49//!
50//! Note that this example uses Egui in the [multi-pass mode]((https://docs.rs/egui/0.31.1/egui/#multi-pass-immediate-mode)).
51//! If you don't want to be limited to the [`EguiPrimaryContextPass`] schedule, you can use the single-pass mode,
52//! but it may get deprecated in the future.
53//!
54//! For more advanced examples, see the [examples](#examples) section below.
55//!
56//! ### Note to developers of public plugins
57//!
58//! If your plugin depends on `bevy_egui`, here are some hints on how to implement the support of both single-pass and multi-pass modes
59//! (with respect to the [`EguiPlugin::enable_multipass_for_primary_context`] flag):
60//! - Don't initialize [`EguiPlugin`] for the user, i.e. DO NOT use `add_plugins(EguiPlugin { ... })` in your code,
61//!   users should be able to opt in or opt out of the multi-pass mode on their own.
62//! - If you add UI systems, make sure they go into the [`EguiPrimaryContextPass`] schedule - this will guarantee your plugin supports both the single-pass and multi-pass modes.
63//!
64//! Your plugin code might look like this:
65//!
66//! ```no_run,rust
67//! # use bevy::prelude::*;
68//! # use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiPrimaryContextPass};
69//!
70//! pub struct MyPlugin;
71//!
72//! impl Plugin for MyPlugin {
73//!     fn build(&self, app: &mut App) {
74//!         // Don't add the plugin for users, let them chose the default mode themselves
75//!         // and just make sure they initialize EguiPlugin before yours.
76//!         assert!(app.is_plugin_added::<EguiPlugin>());
77//!
78//!         app.add_systems(EguiPrimaryContextPass, ui_system);
79//!     }
80//! }
81//!
82//! fn ui_system(contexts: EguiContexts) -> Result {
83//!     // ...
84//!     Ok(())
85//! }
86//! ```
87//!
88//! ## Examples
89//!
90//! To run an example, use the following command (you may replace `ui` with a name of another example):
91//!
92//! ```bash
93//! cargo run --example ui
94//! ```
95//!
96//! ### ui ([live page](https://vladbat00.github.io/bevy_egui/ui), source: [examples/ui.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/ui.rs))
97//!
98//! Showcasing some more advanced UI, rendering images, hidpi scaling.
99//!
100//! ### absorb_input ([live page](https://vladbat00.github.io/bevy_egui/absorb_input), source: [examples/absorb_input.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/absorb_input.rs))
101//!
102//! Demonstrating the available options for absorbing input when Egui is using pointer or keyboard.
103//!
104//! ### color_test ([live page](https://vladbat00.github.io/bevy_egui/color_test), source: [examples/color_test.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/color_test.rs))
105//!
106//! Rendering test from [egui.rs](https://egui.rs). We don't fully pass it, help is wanted ([#291](https://github.com/vladbat00/bevy_egui/issues/291)).
107//!
108//! ### side_panel ([live page](https://vladbat00.github.io/bevy_egui/side_panel), source: [examples/side_panel.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/side_panel.rs))
109//!
110//! Showing how to display an Egui side panel and transform a camera with a perspective projection to make rendering centered relative to the remaining screen area.
111//!
112//! ### split_screen ([live page](https://vladbat00.github.io/bevy_egui/split_screen), source: [examples/split_screen.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/split_screen.rs))
113//!
114//! Demonstrating how to render multiple Egui contexts, attaching them to several cameras that target the same window.
115//!
116//! ### render_egui_to_image ([live page](https://vladbat00.github.io/bevy_egui/render_egui_to_image), source: [examples/render_egui_to_image.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/render_egui_to_image.rs))
117//!
118//! Rendering UI to an image (texture) and then using it as a mesh material texture.
119//!
120//! ### render_to_image_widget ([live page](https://vladbat00.github.io/bevy_egui/render_to_image_widget), source: [examples/render_to_image_widget.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/render_to_image_widget.rs))
121//!
122//! Rendering to a texture with Bevy and showing it as an Egui image widget.
123//!
124//! ### two_windows (source: [examples/two_windows.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/two_windows.rs))
125//!
126//! Setting up two windows with an Egui context for each.
127//!
128//! ### paint_callback ([live page](https://vladbat00.github.io/bevy_egui/paint_callback), source: [examples/paint_callback.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/paint_callback.rs))
129//!
130//! Using Egui paint callbacks.
131//!
132//! ### simple ([live page](https://vladbat00.github.io/bevy_egui/simple), source: [examples/simple.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/simple.rs))
133//!
134//! The minimal usage example from this readme.
135//!
136//! ### run_manually ([live page](https://vladbat00.github.io/bevy_egui/run_manually), source: [examples/run_manually.rs](https://github.com/vladbat00/bevy_egui/blob/v0.35.0/examples/run_manually.rs))
137//!
138//! The same minimal example demonstrating running Egui passes manually.
139//!
140//! ## See also
141//!
142//! - [`bevy-inspector-egui`](https://github.com/jakobhellermann/bevy-inspector-egui)
143
144/// Helpers for converting Bevy types into Egui ones and vice versa.
145pub mod helpers;
146/// Systems for translating Bevy input events into Egui input.
147pub mod input;
148/// Systems for handling Egui output.
149pub mod output;
150/// `bevy_picking` integration for Egui.
151#[cfg(feature = "picking")]
152pub mod picking;
153/// Rendering Egui with [`bevy_render`].
154#[cfg(feature = "render")]
155pub mod render;
156/// Mobile web keyboard input support.
157#[cfg(target_arch = "wasm32")]
158pub mod text_agent;
159/// Clipboard management for web.
160#[cfg(all(feature = "manage_clipboard", target_arch = "wasm32",))]
161pub mod web_clipboard;
162
163pub use egui;
164
165use crate::input::*;
166#[cfg(target_arch = "wasm32")]
167use crate::text_agent::{
168    install_text_agent_system, is_mobile_safari, process_safari_virtual_keyboard_system,
169    write_text_agent_channel_events_system, SafariVirtualKeyboardTouchState, TextAgentChannel,
170    VirtualTouchInfo,
171};
172#[cfg(all(
173    feature = "manage_clipboard",
174    not(any(target_arch = "wasm32", target_os = "android"))
175))]
176use arboard::Clipboard;
177use bevy_app::prelude::*;
178#[cfg(feature = "render")]
179use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle};
180use bevy_derive::{Deref, DerefMut};
181use bevy_ecs::{
182    prelude::*,
183    query::{QueryData, QueryEntityError, QuerySingleError},
184    schedule::{InternedScheduleLabel, ScheduleLabel},
185    system::SystemParam,
186};
187#[cfg(feature = "render")]
188use bevy_image::{Image, ImageSampler};
189use bevy_input::InputSystem;
190#[allow(unused_imports)]
191use bevy_log as log;
192#[cfg(feature = "picking")]
193use bevy_picking::{
194    backend::{HitData, PointerHits},
195    pointer::{PointerId, PointerLocation},
196};
197#[cfg(feature = "render")]
198use bevy_platform::collections::HashMap;
199use bevy_platform::collections::HashSet;
200use bevy_reflect::Reflect;
201#[cfg(feature = "picking")]
202use bevy_render::camera::NormalizedRenderTarget;
203#[cfg(feature = "render")]
204use bevy_render::{
205    extract_resource::{ExtractResource, ExtractResourcePlugin},
206    render_resource::SpecializedRenderPipelines,
207    ExtractSchedule, Render, RenderApp, RenderSet,
208};
209use bevy_winit::cursor::CursorIcon;
210use output::process_output_system;
211#[cfg(all(
212    feature = "manage_clipboard",
213    not(any(target_arch = "wasm32", target_os = "android"))
214))]
215use std::cell::{RefCell, RefMut};
216#[cfg(target_arch = "wasm32")]
217use wasm_bindgen::prelude::*;
218
219/// Adds all Egui resources and render graph nodes.
220pub struct EguiPlugin {
221    /// ## About Egui multi-pass mode
222    ///
223    /// _From the [Egui documentation](https://docs.rs/egui/0.31.1/egui/#multi-pass-immediate-mode):_
224    ///
225    /// By default, egui usually only does one pass for each rendered frame.
226    /// However, egui supports multi-pass immediate mode.
227    /// Another pass can be requested with [`egui::Context::request_discard`].
228    ///
229    /// This is used by some widgets to cover up "first-frame jitters".
230    /// For instance, the [`egui::Grid`] needs to know the width of all columns before it can properly place the widgets.
231    /// But it cannot know the width of widgets to come.
232    /// So it stores the max widths of previous frames and uses that.
233    /// This means the first time a `Grid` is shown it will _guess_ the widths of the columns, and will usually guess wrong.
234    /// This means the contents of the grid will be wrong for one frame, before settling to the correct places.
235    /// Therefore `Grid` calls [`egui::Context::request_discard`] when it is first shown, so the wrong placement is never
236    /// visible to the end user.
237    ///
238    /// ## Usage
239    ///
240    /// Set this to `true` to enable an experimental support for the Egui multi-pass mode.
241    ///
242    /// Enabling the multi-pass mode will require your app to use the new [`EguiPrimaryContextPass`] schedule:
243    ///
244    /// ```no_run,rust
245    /// # use bevy::prelude::*;
246    /// # use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiPrimaryContextPass};
247    /// fn main() {
248    ///     App::new()
249    ///         .add_plugins(DefaultPlugins)
250    ///         .add_plugins(EguiPlugin::default())
251    ///         .add_systems(EguiPrimaryContextPass, ui_example_system)
252    ///         .run();
253    /// }
254    /// fn ui_example_system(contexts: EguiContexts) -> Result {
255    ///     // ...
256    ///     Ok(())
257    /// }
258    /// ```
259    ///
260    /// If you create multiple contexts (for example, when using multiple windows or rendering to an image),
261    /// you need to define a custom schedule and assign it to additional contexts manually:
262    ///
263    /// ```no_run,rust
264    /// # use bevy::prelude::*;
265    /// # use bevy::ecs::schedule::ScheduleLabel;
266    /// # use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiPrimaryContextPass, EguiMultipassSchedule};
267    /// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
268    /// pub struct SecondWindowContextPass;
269    ///
270    /// fn main() {
271    ///     App::new()
272    ///         .add_plugins(DefaultPlugins)
273    ///         .add_plugins(EguiPlugin::default())
274    ///         .add_systems(Startup, create_new_window_system)
275    ///         .add_systems(EguiPrimaryContextPass, ui_example_system)
276    ///         .add_systems(SecondWindowContextPass, ui_example_system)
277    ///         .run();
278    /// }
279    ///
280    /// fn create_new_window_system(mut commands: Commands) {
281    ///     commands.spawn((Window::default(), EguiMultipassSchedule::new(SecondWindowContextPass)));
282    /// }
283    ///
284    /// fn ui_example_system(contexts: EguiContexts) -> Result {
285    ///     // ...
286    ///     Ok(())
287    /// }
288    /// ```
289    ///
290    /// In the future, the multi-pass mode will likely phase the single-pass one out.
291    ///
292    /// ## Note to developers of public plugins
293    ///
294    /// If your plugin depends on `bevy_egui`, here are some hints on how to implement the support of both single-pass and multi-pass modes
295    /// (with respect to the [`EguiPlugin::enable_multipass_for_primary_context`] flag):
296    /// - Don't initialize [`EguiPlugin`] for the user, i.e. DO NOT use `add_plugins(EguiPlugin { ... })` in your code,
297    ///   users should be able to opt in or opt out of the multi-pass mode on their own.
298    /// - If you add UI systems, make sure they go into the [`EguiPrimaryContextPass`] schedule - this will guarantee your plugin supports both the single-pass and multi-pass modes.
299    ///
300    /// Your plugin code might look like this:
301    ///
302    /// ```no_run,rust
303    /// # use bevy::prelude::*;
304    /// # use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiPrimaryContextPass};
305    ///
306    /// pub struct MyPlugin;
307    ///
308    /// impl Plugin for MyPlugin {
309    ///     fn build(&self, app: &mut App) {
310    ///         // Don't add the plugin for users, let them chose the default mode themselves
311    ///         // and just make sure they initialize EguiPlugin before yours.
312    ///         assert!(app.is_plugin_added::<EguiPlugin>());
313    ///
314    ///         app.add_systems(EguiPrimaryContextPass, ui_system);
315    ///     }
316    /// }
317    ///
318    /// fn ui_system(contexts: EguiContexts) -> Result {
319    ///     // ...
320    ///     Ok(())
321    /// }
322    /// ```
323    #[deprecated(
324        note = "The option to disable the multi-pass mode is now deprecated, use `EguiPlugin::default` instead"
325    )]
326    pub enable_multipass_for_primary_context: bool,
327}
328
329impl Default for EguiPlugin {
330    fn default() -> Self {
331        Self {
332            #[allow(deprecated)]
333            enable_multipass_for_primary_context: true,
334        }
335    }
336}
337
338/// A resource for storing global plugin settings.
339#[derive(Clone, Debug, Resource, Reflect)]
340pub struct EguiGlobalSettings {
341    /// Set this to `false` if you want to control the creation of [`EguiContext`] instances manually.
342    ///
343    /// By default, `bevy_egui` will create a context for the first camera an application creates.
344    pub auto_create_primary_context: bool,
345    /// Set this to `false` if you want to disable updating focused contexts by the plugin's systems
346    /// (enabled by default).
347    ///
348    /// For more info, see the [`FocusedNonWindowEguiContext`] documentation.
349    pub enable_focused_non_window_context_updates: bool,
350    /// Controls running of the input systems.
351    pub input_system_settings: EguiInputSystemSettings,
352    /// Controls running of the [`absorb_bevy_input_system`] system, disabled by default.
353    ///
354    /// ## Considerations
355    ///
356    /// Enabling this system makes an assumption that `bevy_egui` takes priority in input handling
357    /// over other plugins and systems. This should work ok as long as there's no other system
358    /// clearing events the same way that might be in conflict with `bevy_egui`, and there's
359    /// no other system that needs a non-interrupted flow of events.
360    ///
361    /// ## Alternative
362    ///
363    /// Apply `run_if(not(egui_wants_any_pointer_input))` or `run_if(not(egui_wants_any_keyboard_input))` to your systems
364    /// that need to be disabled while Egui is using input (see the [`egui_wants_any_pointer_input`], [`egui_wants_any_keyboard_input`] run conditions).
365    pub enable_absorb_bevy_input_system: bool,
366    /// Controls whether `bevy_egui` updates [`CursorIcon`], enabled by default.
367    ///
368    /// If you want to have custom cursor icons in your app, set this to `false` to avoid Egui
369    /// overriding the icons.
370    pub enable_cursor_icon_updates: bool,
371}
372
373impl Default for EguiGlobalSettings {
374    fn default() -> Self {
375        Self {
376            auto_create_primary_context: true,
377            enable_focused_non_window_context_updates: true,
378            input_system_settings: EguiInputSystemSettings::default(),
379            enable_absorb_bevy_input_system: false,
380            enable_cursor_icon_updates: true,
381        }
382    }
383}
384
385/// This resource is created if [`EguiPlugin`] is initialized with [`EguiPlugin::enable_multipass_for_primary_context`] set to `true`.
386#[derive(Resource)]
387pub struct EnableMultipassForPrimaryContext;
388
389/// A component for storing Egui context settings.
390#[derive(Clone, Debug, Component, Reflect)]
391pub struct EguiContextSettings {
392    /// If set to `true`, a user is expected to call [`egui::Context::run`] or [`egui::Context::begin_pass`] and [`egui::Context::end_pass`] manually.
393    pub run_manually: bool,
394    /// Global scale factor for Egui widgets (`1.0` by default).
395    ///
396    /// This setting can be used to force the UI to render in physical pixels regardless of DPI as follows:
397    /// ```rust
398    /// use bevy::{prelude::*, window::PrimaryWindow};
399    /// use bevy_egui::EguiContextSettings;
400    ///
401    /// fn update_ui_scale_factor(mut egui_contexts: Query<(&mut EguiContextSettings, &Camera)>) {
402    ///     for (mut egui_settings, camera) in egui_contexts {
403    ///         egui_settings.scale_factor = 1.0 / camera.target_scaling_factor().unwrap_or(1.0);
404    ///     }
405    /// }
406    /// ```
407    pub scale_factor: f32,
408    /// Is used as a default value for hyperlink [target](https://www.w3schools.com/tags/att_a_target.asp) hints.
409    /// If not specified, `_self` will be used. Only matters in a web browser.
410    #[cfg(feature = "open_url")]
411    pub default_open_url_target: Option<String>,
412    /// Controls if Egui should capture pointer input when using [`bevy_picking`] (i.e. suppress `bevy_picking` events when a pointer is over an Egui window).
413    #[cfg(feature = "picking")]
414    pub capture_pointer_input: bool,
415    /// Controls running of the input systems.
416    pub input_system_settings: EguiInputSystemSettings,
417    /// Controls whether `bevy_egui` updates [`CursorIcon`], enabled by default.
418    ///
419    /// If you want to have custom cursor icons in your app, set this to `false` to avoid Egui
420    /// overriding the icons.
421    pub enable_cursor_icon_updates: bool,
422}
423
424// Just to keep the PartialEq
425impl PartialEq for EguiContextSettings {
426    #[allow(clippy::let_and_return)]
427    fn eq(&self, other: &Self) -> bool {
428        let eq = self.scale_factor == other.scale_factor;
429        #[cfg(feature = "open_url")]
430        let eq = eq && self.default_open_url_target == other.default_open_url_target;
431        eq
432    }
433}
434
435impl Default for EguiContextSettings {
436    fn default() -> Self {
437        Self {
438            run_manually: false,
439            scale_factor: 1.0,
440            #[cfg(feature = "open_url")]
441            default_open_url_target: None,
442            #[cfg(feature = "picking")]
443            capture_pointer_input: true,
444            input_system_settings: EguiInputSystemSettings::default(),
445            enable_cursor_icon_updates: true,
446        }
447    }
448}
449
450#[derive(Clone, Debug, Reflect, PartialEq, Eq)]
451/// All the systems are enabled by default. These settings exist within both [`EguiGlobalSettings`] and [`EguiContextSettings`].
452pub struct EguiInputSystemSettings {
453    /// Controls running of the [`write_modifiers_keys_state_system`] system.
454    pub run_write_modifiers_keys_state_system: bool,
455    /// Controls running of the [`write_window_pointer_moved_events_system`] system.
456    pub run_write_window_pointer_moved_events_system: bool,
457    /// Controls running of the [`write_pointer_button_events_system`] system.
458    pub run_write_pointer_button_events_system: bool,
459    /// Controls running of the [`write_window_touch_events_system`] system.
460    pub run_write_window_touch_events_system: bool,
461    /// Controls running of the [`write_non_window_pointer_moved_events_system`] system.
462    pub run_write_non_window_pointer_moved_events_system: bool,
463    /// Controls running of the [`write_mouse_wheel_events_system`] system.
464    pub run_write_mouse_wheel_events_system: bool,
465    /// Controls running of the [`write_non_window_touch_events_system`] system.
466    pub run_write_non_window_touch_events_system: bool,
467    /// Controls running of the [`write_keyboard_input_events_system`] system.
468    pub run_write_keyboard_input_events_system: bool,
469    /// Controls running of the [`write_ime_events_system`] system.
470    pub run_write_ime_events_system: bool,
471    /// Controls running of the [`write_file_dnd_events_system`] system.
472    pub run_write_file_dnd_events_system: bool,
473    /// Controls running of the [`write_text_agent_channel_events_system`] system.
474    #[cfg(target_arch = "wasm32")]
475    pub run_write_text_agent_channel_events_system: bool,
476    /// Controls running of the [`web_clipboard::write_web_clipboard_events_system`] system.
477    #[cfg(all(feature = "manage_clipboard", target_arch = "wasm32"))]
478    pub run_write_web_clipboard_events_system: bool,
479}
480
481impl Default for EguiInputSystemSettings {
482    fn default() -> Self {
483        Self {
484            run_write_modifiers_keys_state_system: true,
485            run_write_window_pointer_moved_events_system: true,
486            run_write_pointer_button_events_system: true,
487            run_write_window_touch_events_system: true,
488            run_write_non_window_pointer_moved_events_system: true,
489            run_write_mouse_wheel_events_system: true,
490            run_write_non_window_touch_events_system: true,
491            run_write_keyboard_input_events_system: true,
492            run_write_ime_events_system: true,
493            run_write_file_dnd_events_system: true,
494            #[cfg(target_arch = "wasm32")]
495            run_write_text_agent_channel_events_system: true,
496            #[cfg(all(feature = "manage_clipboard", target_arch = "wasm32"))]
497            run_write_web_clipboard_events_system: true,
498        }
499    }
500}
501
502/// Use this schedule to run your UI systems with the primary Egui context.
503/// (Mandatory if the context is running in the multi-pass mode.)
504#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
505pub struct EguiPrimaryContextPass;
506
507/// A marker component for a primary Egui context.
508#[derive(Component, Clone)]
509#[require(EguiMultipassSchedule::new(EguiPrimaryContextPass))]
510pub struct PrimaryEguiContext;
511
512/// Add this component to your additional Egui contexts (e.g. when rendering to a new window or an image),
513/// to enable multi-pass support. Note that each Egui context running in the multi-pass mode must use a unique schedule.
514#[derive(Component, Clone)]
515#[require(EguiContext)]
516pub struct EguiMultipassSchedule(pub InternedScheduleLabel);
517
518impl EguiMultipassSchedule {
519    /// Constructs the component from a schedule label.
520    pub fn new(schedule: impl ScheduleLabel) -> Self {
521        Self(schedule.intern())
522    }
523}
524
525/// Is used for storing Egui context input.
526///
527/// It gets reset during the [`crate::EguiInputSet::WriteEguiEvents`] system set.
528#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
529pub struct EguiInput(pub egui::RawInput);
530
531/// Intermediate output buffer generated on an Egui pass end and consumed by the [`process_output_system`] system.
532#[derive(Component, Clone, Default, Deref, DerefMut)]
533pub struct EguiFullOutput(pub Option<egui::FullOutput>);
534
535/// A resource for accessing clipboard.
536///
537/// The resource is available only if `manage_clipboard` feature is enabled.
538#[cfg(all(feature = "manage_clipboard", not(target_os = "android")))]
539#[derive(Default, Resource)]
540pub struct EguiClipboard {
541    #[cfg(not(target_arch = "wasm32"))]
542    clipboard: thread_local::ThreadLocal<Option<RefCell<Clipboard>>>,
543    #[cfg(target_arch = "wasm32")]
544    clipboard: web_clipboard::WebClipboard,
545}
546
547/// Is used for storing Egui shapes and textures delta.
548#[derive(Component, Clone, Default, Debug)]
549pub struct EguiRenderOutput {
550    /// Pairs of rectangles and paint commands.
551    ///
552    /// The field gets populated during the [`EguiPostUpdateSet::ProcessOutput`] system (belonging to bevy's [`PostUpdate`])
553    /// and processed during [`render::EguiPassNode`]'s `update`.
554    pub paint_jobs: Vec<egui::ClippedPrimitive>,
555    /// The change in egui textures since last frame.
556    pub textures_delta: egui::TexturesDelta,
557}
558
559impl EguiRenderOutput {
560    /// Returns `true` if the output has no Egui shapes and no textures delta.
561    pub fn is_empty(&self) -> bool {
562        self.paint_jobs.is_empty() && self.textures_delta.is_empty()
563    }
564}
565
566/// Stores last Egui output.
567#[derive(Component, Clone, Default)]
568pub struct EguiOutput {
569    /// The field gets updated during [`process_output_system`] (in the [`EguiPostUpdateSet::ProcessOutput`] set, belonging to [`PostUpdate`]).
570    pub platform_output: egui::PlatformOutput,
571}
572
573/// A component for storing `bevy_egui` context.
574#[derive(Clone, Component, Default)]
575#[require(
576    EguiContextSettings,
577    EguiInput,
578    EguiContextPointerPosition,
579    EguiContextPointerTouchId,
580    EguiContextImeState,
581    EguiFullOutput,
582    EguiRenderOutput,
583    EguiOutput,
584    CursorIcon
585)]
586pub struct EguiContext {
587    ctx: egui::Context,
588}
589
590impl EguiContext {
591    /// Borrows the underlying Egui context immutably.
592    ///
593    /// Even though the mutable borrow isn't necessary, as the context is wrapped into `RwLock`,
594    /// using the immutable getter is gated with the `immutable_ctx` feature. Using the immutable
595    /// borrow is discouraged as it may cause unpredictable blocking in UI systems.
596    ///
597    /// When the context is queried with `&mut EguiContext`, the Bevy scheduler is able to make
598    /// sure that the context isn't accessed concurrently and can perform other useful work
599    /// instead of busy-waiting.
600    #[cfg(feature = "immutable_ctx")]
601    #[must_use]
602    pub fn get(&self) -> &egui::Context {
603        &self.ctx
604    }
605
606    /// Borrows the underlying Egui context mutably.
607    ///
608    /// Even though the mutable borrow isn't necessary, as the context is wrapped into `RwLock`,
609    /// using the immutable getter is gated with the `immutable_ctx` feature. Using the immutable
610    /// borrow is discouraged as it may cause unpredictable blocking in UI systems.
611    ///
612    /// When the context is queried with `&mut EguiContext`, the Bevy scheduler is able to make
613    /// sure that the context isn't accessed concurrently and can perform other useful work
614    /// instead of busy-waiting.
615    #[must_use]
616    pub fn get_mut(&mut self) -> &mut egui::Context {
617        &mut self.ctx
618    }
619}
620
621// This query is actually unused, but we use it just to cheat a relevant error message.
622type EguiContextsPrimaryQuery<'w, 's> =
623    Query<'w, 's, &'static mut EguiContext, With<PrimaryEguiContext>>;
624
625type EguiContextsQuery<'w, 's> = Query<
626    'w,
627    's,
628    (
629        &'static mut EguiContext,
630        Option<&'static PrimaryEguiContext>,
631    ),
632>;
633
634#[derive(SystemParam)]
635/// A helper SystemParam that provides a way to get [`EguiContext`] with less boilerplate and
636/// combines a proxy interface to the [`EguiUserTextures`] resource.
637pub struct EguiContexts<'w, 's> {
638    q: EguiContextsQuery<'w, 's>,
639    #[cfg(feature = "render")]
640    user_textures: ResMut<'w, EguiUserTextures>,
641}
642
643#[allow(clippy::manual_try_fold)]
644impl EguiContexts<'_, '_> {
645    /// Returns an Egui context with the [`PrimaryEguiContext`] component.
646    #[inline]
647    pub fn ctx_mut(&mut self) -> Result<&mut egui::Context, QuerySingleError> {
648        self.q.iter_mut().fold(
649            Err(QuerySingleError::NoEntities(core::any::type_name::<
650                EguiContextsPrimaryQuery,
651            >())),
652            |result, (ctx, primary)| match (&result, primary) {
653                (Err(QuerySingleError::MultipleEntities(_)), _) => result,
654                (Err(QuerySingleError::NoEntities(_)), Some(_)) => Ok(ctx.into_inner().get_mut()),
655                (Err(QuerySingleError::NoEntities(_)), None) => result,
656                (Ok(_), Some(_)) => {
657                    Err(QuerySingleError::MultipleEntities(core::any::type_name::<
658                        EguiContextsPrimaryQuery,
659                    >()))
660                }
661                (Ok(_), None) => result,
662            },
663        )
664    }
665
666    /// Egui context of a specific entity.
667    #[inline]
668    pub fn ctx_for_entity_mut(
669        &mut self,
670        entity: Entity,
671    ) -> Result<&mut egui::Context, QueryEntityError> {
672        self.q
673            .get_mut(entity)
674            .map(|(context, _primary)| context.into_inner().get_mut())
675    }
676
677    /// Allows to get multiple contexts at the same time. This function is useful when you want
678    /// to get multiple contexts without using the `immutable_ctx` feature.
679    #[inline]
680    pub fn ctx_for_entities_mut<const N: usize>(
681        &mut self,
682        ids: [Entity; N],
683    ) -> Result<[&mut egui::Context; N], QueryEntityError> {
684        self.q
685            .get_many_mut(ids)
686            .map(|arr| arr.map(|(ctx, _primary_window)| ctx.into_inner().get_mut()))
687    }
688
689    /// Returns an Egui context with the [`PrimaryEguiContext`] component.
690    ///
691    /// Even though the mutable borrow isn't necessary, as the context is wrapped into `RwLock`,
692    /// using the immutable getter is gated with the `immutable_ctx` feature. Using the immutable
693    /// borrow is discouraged as it may cause unpredictable blocking in UI systems.
694    ///
695    /// When the context is queried with `&mut EguiContext`, the Bevy scheduler is able to make
696    /// sure that the context isn't accessed concurrently and can perform other useful work
697    /// instead of busy-waiting.
698    #[cfg(feature = "immutable_ctx")]
699    #[inline]
700    pub fn ctx(&self) -> Result<&egui::Context, QuerySingleError> {
701        self.q.iter().fold(
702            Err(QuerySingleError::NoEntities(core::any::type_name::<
703                EguiContextsPrimaryQuery,
704            >())),
705            |result, (ctx, primary)| match (&result, primary) {
706                (Err(QuerySingleError::MultipleEntities(_)), _) => result,
707                (Err(QuerySingleError::NoEntities(_)), Some(_)) => Ok(ctx.get()),
708                (Err(QuerySingleError::NoEntities(_)), None) => result,
709                (Ok(_), Some(_)) => {
710                    Err(QuerySingleError::MultipleEntities(core::any::type_name::<
711                        EguiContextsPrimaryQuery,
712                    >()))
713                }
714                (Ok(_), None) => result,
715            },
716        )
717    }
718
719    /// Egui context of a specific entity.
720    ///
721    /// Even though the mutable borrow isn't necessary, as the context is wrapped into `RwLock`,
722    /// using the immutable getter is gated with the `immutable_ctx` feature. Using the immutable
723    /// borrow is discouraged as it may cause unpredictable blocking in UI systems.
724    ///
725    /// When the context is queried with `&mut EguiContext`, the Bevy scheduler is able to make
726    /// sure that the context isn't accessed concurrently and can perform other useful work
727    /// instead of busy-waiting.
728    #[inline]
729    #[cfg(feature = "immutable_ctx")]
730    pub fn ctx_for_entity(&self, entity: Entity) -> Result<&egui::Context, QueryEntityError> {
731        self.q.get(entity).map(|(context, _primary)| context.get())
732    }
733
734    /// Can accept either a strong or a weak handle.
735    ///
736    /// You may want to pass a weak handle if you control removing texture assets in your
737    /// application manually and don't want to bother with cleaning up textures in Egui.
738    /// (The cleanup happens in [`free_egui_textures_system`].)
739    ///
740    /// You'll want to pass a strong handle if a texture is used only in Egui and there are no
741    /// handle copies stored anywhere else.
742    #[cfg(feature = "render")]
743    pub fn add_image(&mut self, image: Handle<Image>) -> egui::TextureId {
744        self.user_textures.add_image(image)
745    }
746
747    /// Removes the image handle and an Egui texture id associated with it.
748    #[cfg(feature = "render")]
749    #[track_caller]
750    pub fn remove_image(&mut self, image: &Handle<Image>) -> Option<egui::TextureId> {
751        self.user_textures.remove_image(image)
752    }
753
754    /// Returns an associated Egui texture id.
755    #[cfg(feature = "render")]
756    #[must_use]
757    #[track_caller]
758    pub fn image_id(&self, image: &Handle<Image>) -> Option<egui::TextureId> {
759        self.user_textures.image_id(image)
760    }
761}
762
763/// A resource for storing `bevy_egui` user textures.
764#[derive(Clone, Resource, ExtractResource)]
765#[cfg(feature = "render")]
766pub struct EguiUserTextures {
767    textures: HashMap<Handle<Image>, u64>,
768    free_list: Vec<u64>,
769}
770
771#[cfg(feature = "render")]
772impl Default for EguiUserTextures {
773    fn default() -> Self {
774        Self {
775            textures: HashMap::default(),
776            free_list: vec![0],
777        }
778    }
779}
780
781#[cfg(feature = "render")]
782impl EguiUserTextures {
783    /// Can accept either a strong or a weak handle.
784    ///
785    /// You may want to pass a weak handle if you control removing texture assets in your
786    /// application manually and don't want to bother with cleaning up textures in Egui.
787    /// (The cleanup happens in [`free_egui_textures_system`].)
788    ///
789    /// You'll want to pass a strong handle if a texture is used only in Egui and there are no
790    /// handle copies stored anywhere else.
791    pub fn add_image(&mut self, image: Handle<Image>) -> egui::TextureId {
792        let id = *self.textures.entry(image.clone()).or_insert_with(|| {
793            let id = self
794                .free_list
795                .pop()
796                .expect("free list must contain at least 1 element");
797            log::debug!("Add a new image (id: {}, handle: {:?})", id, image);
798            if self.free_list.is_empty() {
799                self.free_list.push(id.checked_add(1).expect("out of ids"));
800            }
801            id
802        });
803        egui::TextureId::User(id)
804    }
805
806    /// Removes the image handle and an Egui texture id associated with it.
807    pub fn remove_image(&mut self, image: &Handle<Image>) -> Option<egui::TextureId> {
808        let id = self.textures.remove(image);
809        log::debug!("Remove image (id: {:?}, handle: {:?})", id, image);
810        if let Some(id) = id {
811            self.free_list.push(id);
812        }
813        id.map(egui::TextureId::User)
814    }
815
816    /// Returns an associated Egui texture id.
817    #[must_use]
818    pub fn image_id(&self, image: &Handle<Image>) -> Option<egui::TextureId> {
819        self.textures
820            .get(image)
821            .map(|&id| egui::TextureId::User(id))
822    }
823}
824
825/// Stores physical size and scale factor, is used as a helper to calculate logical size.
826/// The component lives only in the Render world.
827#[derive(Component, Debug, Default, Clone, Copy, PartialEq)]
828pub struct RenderComputedScaleFactor {
829    /// Scale factor ([`EguiContextSettings::scale_factor`] multiplied by [`bevy_render::camera::Camera::target_scaling_factor`]).
830    pub scale_factor: f32,
831}
832
833/// The names of `bevy_egui` nodes.
834pub mod node {
835    /// The main egui pass.
836    pub const EGUI_PASS: &str = "egui_pass";
837}
838
839#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
840/// The `bevy_egui` plugin startup system sets.
841pub enum EguiStartupSet {
842    /// Initializes a primary Egui context (see [`setup_primary_egui_context_system`]).
843    InitContexts,
844}
845
846/// System sets that run during the [`PreUpdate`] schedule.
847#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
848pub enum EguiPreUpdateSet {
849    /// Initializes Egui contexts for newly created render targets.
850    InitContexts,
851    /// Reads Egui inputs (keyboard, mouse, etc) and writes them into the [`EguiInput`] resource.
852    ///
853    /// To modify the input, you can hook your system like this:
854    ///
855    /// `system.after(EguiPreUpdateSet::ProcessInput).before(EguiSet::BeginPass)`.
856    ProcessInput,
857    /// Begins the `egui` pass.
858    BeginPass,
859}
860
861/// Subsets of the [`EguiPreUpdateSet::ProcessInput`] set.
862#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
863pub enum EguiInputSet {
864    /// Reads key modifiers state and pointer positions.
865    ///
866    /// This is where [`HoveredNonWindowEguiContext`] should get inserted or removed.
867    InitReading,
868    /// Processes window mouse button click and touch events, updates [`FocusedNonWindowEguiContext`] based on [`HoveredNonWindowEguiContext`].
869    FocusContext,
870    /// Processes rest of the events for both window and non-window contexts.
871    ReadBevyEvents,
872    /// Feeds all the events into [`EguiInput`].
873    WriteEguiEvents,
874}
875
876/// System sets that run during the [`PostUpdate`] schedule.
877#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
878pub enum EguiPostUpdateSet {
879    /// Ends Egui pass.
880    EndPass,
881    /// Processes Egui output, reads paint jobs for the renderer.
882    ProcessOutput,
883    /// Post-processing of Egui output (updates textures, browser virtual keyboard state, etc).
884    PostProcessOutput,
885}
886
887impl Plugin for EguiPlugin {
888    fn build(&self, app: &mut App) {
889        app.register_type::<EguiGlobalSettings>();
890        app.register_type::<EguiContextSettings>();
891        app.init_resource::<EguiGlobalSettings>();
892        app.init_resource::<ModifierKeysState>();
893        app.init_resource::<EguiWantsInput>();
894        app.init_resource::<WindowToEguiContextMap>();
895        app.add_event::<EguiInputEvent>();
896        app.add_event::<EguiFileDragAndDropEvent>();
897
898        #[allow(deprecated)]
899        if self.enable_multipass_for_primary_context {
900            app.insert_resource(EnableMultipassForPrimaryContext);
901        }
902
903        #[cfg(feature = "render")]
904        {
905            app.init_resource::<EguiManagedTextures>();
906            app.init_resource::<EguiUserTextures>();
907            app.add_plugins(ExtractResourcePlugin::<EguiUserTextures>::default());
908            app.add_plugins(ExtractResourcePlugin::<
909                render::systems::ExtractedEguiManagedTextures,
910            >::default());
911        }
912
913        #[cfg(target_arch = "wasm32")]
914        app.init_non_send_resource::<SubscribedEvents>();
915
916        #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))]
917        app.init_resource::<EguiClipboard>();
918
919        app.configure_sets(
920            PreUpdate,
921            (
922                EguiPreUpdateSet::InitContexts,
923                EguiPreUpdateSet::ProcessInput.after(InputSystem),
924                EguiPreUpdateSet::BeginPass,
925            )
926                .chain(),
927        );
928        app.configure_sets(
929            PreUpdate,
930            (
931                EguiInputSet::InitReading,
932                EguiInputSet::FocusContext,
933                EguiInputSet::ReadBevyEvents,
934                EguiInputSet::WriteEguiEvents,
935            )
936                .chain(),
937        );
938        #[cfg(not(feature = "accesskit_placeholder"))]
939        app.configure_sets(
940            PostUpdate,
941            (
942                EguiPostUpdateSet::EndPass,
943                EguiPostUpdateSet::ProcessOutput,
944                EguiPostUpdateSet::PostProcessOutput,
945            )
946                .chain(),
947        );
948        #[cfg(feature = "accesskit_placeholder")]
949        app.configure_sets(
950            PostUpdate,
951            (
952                EguiPostUpdateSet::EndPass,
953                EguiPostUpdateSet::ProcessOutput,
954                EguiPostUpdateSet::PostProcessOutput.before(bevy_a11y::AccessibilitySystem::Update),
955            )
956                .chain(),
957        );
958
959        // Startup systems.
960        #[cfg(all(feature = "manage_clipboard", target_arch = "wasm32"))]
961        {
962            app.add_systems(PreStartup, web_clipboard::startup_setup_web_events_system);
963        }
964        #[cfg(feature = "render")]
965        app.add_systems(
966            PreStartup,
967            (
968                (setup_primary_egui_context_system, ApplyDeferred)
969                    .run_if(|s: Res<EguiGlobalSettings>| s.auto_create_primary_context),
970                update_ui_size_and_scale_system,
971            )
972                .chain()
973                .in_set(EguiStartupSet::InitContexts),
974        );
975
976        // PreUpdate systems.
977        #[cfg(feature = "render")]
978        app.add_systems(
979            PreUpdate,
980            (
981                setup_primary_egui_context_system
982                    .run_if(|s: Res<EguiGlobalSettings>| s.auto_create_primary_context),
983                WindowToEguiContextMap::on_egui_context_added_system,
984                WindowToEguiContextMap::on_egui_context_removed_system,
985                ApplyDeferred,
986                update_ui_size_and_scale_system,
987            )
988                .chain()
989                .in_set(EguiPreUpdateSet::InitContexts),
990        );
991        app.add_systems(
992            PreUpdate,
993            (
994                (
995                    write_modifiers_keys_state_system.run_if(input_system_is_enabled(|s| {
996                        s.run_write_modifiers_keys_state_system
997                    })),
998                    write_window_pointer_moved_events_system.run_if(input_system_is_enabled(|s| {
999                        s.run_write_window_pointer_moved_events_system
1000                    })),
1001                )
1002                    .in_set(EguiInputSet::InitReading),
1003                (
1004                    write_pointer_button_events_system.run_if(input_system_is_enabled(|s| {
1005                        s.run_write_pointer_button_events_system
1006                    })),
1007                    write_window_touch_events_system.run_if(input_system_is_enabled(|s| {
1008                        s.run_write_window_touch_events_system
1009                    })),
1010                )
1011                    .in_set(EguiInputSet::FocusContext),
1012                (
1013                    write_non_window_pointer_moved_events_system.run_if(input_system_is_enabled(
1014                        |s| s.run_write_non_window_pointer_moved_events_system,
1015                    )),
1016                    write_non_window_touch_events_system.run_if(input_system_is_enabled(|s| {
1017                        s.run_write_non_window_touch_events_system
1018                    })),
1019                    write_mouse_wheel_events_system.run_if(input_system_is_enabled(|s| {
1020                        s.run_write_mouse_wheel_events_system
1021                    })),
1022                    write_keyboard_input_events_system.run_if(input_system_is_enabled(|s| {
1023                        s.run_write_keyboard_input_events_system
1024                    })),
1025                    write_ime_events_system
1026                        .run_if(input_system_is_enabled(|s| s.run_write_ime_events_system)),
1027                    write_file_dnd_events_system.run_if(input_system_is_enabled(|s| {
1028                        s.run_write_file_dnd_events_system
1029                    })),
1030                )
1031                    .in_set(EguiInputSet::ReadBevyEvents),
1032                (
1033                    write_egui_input_system,
1034                    absorb_bevy_input_system.run_if(|settings: Res<EguiGlobalSettings>| {
1035                        settings.enable_absorb_bevy_input_system
1036                    }),
1037                )
1038                    .in_set(EguiInputSet::WriteEguiEvents),
1039            )
1040                .chain()
1041                .in_set(EguiPreUpdateSet::ProcessInput),
1042        );
1043        app.add_systems(
1044            PreUpdate,
1045            begin_pass_system.in_set(EguiPreUpdateSet::BeginPass),
1046        );
1047
1048        // Web-specific resources and systems.
1049        #[cfg(target_arch = "wasm32")]
1050        {
1051            use std::sync::{LazyLock, Mutex};
1052
1053            let maybe_window_plugin = app.get_added_plugins::<bevy_window::WindowPlugin>();
1054
1055            if !maybe_window_plugin.is_empty()
1056                && maybe_window_plugin[0].primary_window.is_some()
1057                && maybe_window_plugin[0]
1058                    .primary_window
1059                    .as_ref()
1060                    .unwrap()
1061                    .prevent_default_event_handling
1062            {
1063                app.init_resource::<TextAgentChannel>();
1064
1065                let (sender, receiver) = crossbeam_channel::unbounded();
1066                static TOUCH_INFO: LazyLock<Mutex<VirtualTouchInfo>> =
1067                    LazyLock::new(|| Mutex::new(VirtualTouchInfo::default()));
1068
1069                app.insert_resource(SafariVirtualKeyboardTouchState {
1070                    sender,
1071                    receiver,
1072                    touch_info: &TOUCH_INFO,
1073                });
1074
1075                app.add_systems(
1076                    PreStartup,
1077                    install_text_agent_system.in_set(EguiStartupSet::InitContexts),
1078                );
1079
1080                app.add_systems(
1081                    PreUpdate,
1082                    write_text_agent_channel_events_system
1083                        .run_if(input_system_is_enabled(|s| {
1084                            s.run_write_text_agent_channel_events_system
1085                        }))
1086                        .in_set(EguiPreUpdateSet::ProcessInput)
1087                        .in_set(EguiInputSet::ReadBevyEvents),
1088                );
1089
1090                if is_mobile_safari() {
1091                    app.add_systems(
1092                        PostUpdate,
1093                        process_safari_virtual_keyboard_system
1094                            .in_set(EguiPostUpdateSet::PostProcessOutput),
1095                    );
1096                }
1097            }
1098
1099            #[cfg(feature = "manage_clipboard")]
1100            app.add_systems(
1101                PreUpdate,
1102                web_clipboard::write_web_clipboard_events_system
1103                    .run_if(input_system_is_enabled(|s| {
1104                        s.run_write_web_clipboard_events_system
1105                    }))
1106                    .in_set(EguiPreUpdateSet::ProcessInput)
1107                    .in_set(EguiInputSet::ReadBevyEvents),
1108            );
1109        }
1110
1111        // PostUpdate systems.
1112        app.add_systems(
1113            PostUpdate,
1114            (run_egui_context_pass_loop_system, end_pass_system)
1115                .chain()
1116                .in_set(EguiPostUpdateSet::EndPass),
1117        );
1118        app.add_systems(
1119            PostUpdate,
1120            (
1121                process_output_system,
1122                write_egui_wants_input_system,
1123                #[cfg(any(target_os = "ios", target_os = "android"))]
1124                // show the virtual keyboard on mobile devices
1125                set_ime_allowed_system,
1126            )
1127                .in_set(EguiPostUpdateSet::ProcessOutput),
1128        );
1129        #[cfg(feature = "picking")]
1130        if app.is_plugin_added::<bevy_picking::PickingPlugin>() {
1131            app.add_systems(PostUpdate, capture_pointer_input_system);
1132        } else {
1133            log::warn!("The `bevy_egui/picking` feature is enabled, but `PickingPlugin` is not added (if you use Bevy's `DefaultPlugins`, make sure the `bevy/bevy_picking` feature is enabled too)");
1134        }
1135
1136        #[cfg(feature = "render")]
1137        app.add_systems(
1138            PostUpdate,
1139            update_egui_textures_system.in_set(EguiPostUpdateSet::PostProcessOutput),
1140        )
1141        .add_systems(
1142            Render,
1143            render::systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare),
1144        )
1145        .add_systems(
1146            Render,
1147            render::systems::queue_bind_groups_system.in_set(RenderSet::Queue),
1148        )
1149        .add_systems(
1150            Render,
1151            render::systems::queue_pipelines_system.in_set(RenderSet::Queue),
1152        )
1153        .add_systems(Last, free_egui_textures_system);
1154
1155        #[cfg(feature = "render")]
1156        {
1157            load_internal_asset!(
1158                app,
1159                render::EGUI_SHADER_HANDLE,
1160                "render/egui.wgsl",
1161                bevy_render::render_resource::Shader::from_wgsl
1162            );
1163
1164            let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
1165                return;
1166            };
1167
1168            let egui_graph_2d = render::get_egui_graph(render_app);
1169            let egui_graph_3d = render::get_egui_graph(render_app);
1170            let mut graph = render_app
1171                .world_mut()
1172                .resource_mut::<bevy_render::render_graph::RenderGraph>();
1173
1174            if let Some(graph_2d) =
1175                graph.get_sub_graph_mut(bevy_core_pipeline::core_2d::graph::Core2d)
1176            {
1177                graph_2d.add_sub_graph(render::graph::SubGraphEgui, egui_graph_2d);
1178                graph_2d.add_node(
1179                    render::graph::NodeEgui::EguiPass,
1180                    render::RunEguiSubgraphOnEguiViewNode,
1181                );
1182                graph_2d.add_node_edge(
1183                    bevy_core_pipeline::core_2d::graph::Node2d::EndMainPass,
1184                    render::graph::NodeEgui::EguiPass,
1185                );
1186                graph_2d.add_node_edge(
1187                    bevy_core_pipeline::core_2d::graph::Node2d::EndMainPassPostProcessing,
1188                    render::graph::NodeEgui::EguiPass,
1189                );
1190                graph_2d.add_node_edge(
1191                    render::graph::NodeEgui::EguiPass,
1192                    bevy_core_pipeline::core_2d::graph::Node2d::Upscaling,
1193                );
1194            }
1195
1196            if let Some(graph_3d) =
1197                graph.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::Core3d)
1198            {
1199                graph_3d.add_sub_graph(render::graph::SubGraphEgui, egui_graph_3d);
1200                graph_3d.add_node(
1201                    render::graph::NodeEgui::EguiPass,
1202                    render::RunEguiSubgraphOnEguiViewNode,
1203                );
1204                graph_3d.add_node_edge(
1205                    bevy_core_pipeline::core_3d::graph::Node3d::EndMainPass,
1206                    render::graph::NodeEgui::EguiPass,
1207                );
1208                graph_3d.add_node_edge(
1209                    bevy_core_pipeline::core_3d::graph::Node3d::EndMainPassPostProcessing,
1210                    render::graph::NodeEgui::EguiPass,
1211                );
1212                graph_3d.add_node_edge(
1213                    render::graph::NodeEgui::EguiPass,
1214                    bevy_core_pipeline::core_3d::graph::Node3d::Upscaling,
1215                );
1216            }
1217        }
1218
1219        #[cfg(feature = "accesskit_placeholder")]
1220        app.add_systems(
1221            PostUpdate,
1222            update_accessibility_system.in_set(EguiPostUpdateSet::PostProcessOutput),
1223        );
1224    }
1225
1226    #[cfg(feature = "render")]
1227    fn finish(&self, app: &mut App) {
1228        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
1229            render_app
1230                .init_resource::<render::EguiPipeline>()
1231                .init_resource::<SpecializedRenderPipelines<render::EguiPipeline>>()
1232                .init_resource::<render::systems::EguiTransforms>()
1233                .init_resource::<render::systems::EguiRenderData>()
1234                .add_systems(
1235                    // Seems to be just the set to add/remove nodes, as it'll run before
1236                    // `RenderSet::ExtractCommands` where render nodes get updated.
1237                    ExtractSchedule,
1238                    render::extract_egui_camera_view_system,
1239                )
1240                .add_systems(
1241                    Render,
1242                    render::systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare),
1243                )
1244                .add_systems(
1245                    Render,
1246                    render::systems::prepare_egui_render_target_data_system
1247                        .in_set(RenderSet::Prepare),
1248                )
1249                .add_systems(
1250                    Render,
1251                    render::systems::queue_bind_groups_system.in_set(RenderSet::Queue),
1252                )
1253                .add_systems(
1254                    Render,
1255                    render::systems::queue_pipelines_system.in_set(RenderSet::Queue),
1256                );
1257        }
1258    }
1259}
1260
1261fn input_system_is_enabled(
1262    test: impl Fn(&EguiInputSystemSettings) -> bool,
1263) -> impl Fn(Res<EguiGlobalSettings>) -> bool {
1264    move |settings| test(&settings.input_system_settings)
1265}
1266
1267/// Contains textures allocated and painted by Egui.
1268#[cfg(feature = "render")]
1269#[derive(Resource, Deref, DerefMut, Default)]
1270pub struct EguiManagedTextures(pub HashMap<(Entity, u64), EguiManagedTexture>);
1271
1272/// Represents a texture allocated and painted by Egui.
1273#[cfg(feature = "render")]
1274pub struct EguiManagedTexture {
1275    /// Assets store handle.
1276    pub handle: Handle<Image>,
1277    /// Stored in full so we can do partial updates (which bevy doesn't support).
1278    pub color_image: egui::ColorImage,
1279}
1280
1281/// Adds bevy_egui components to a first found camera assuming it's a primary one.
1282///
1283/// To disable this behavior, set [`EguiGlobalSettings::auto_create_primary_context`] to `false` before you create your first camera.
1284/// When spawning a camera to which you want to attach the primary Egui context, insert the [`EguiPrimaryContextPass`] component into the respective camera entity.
1285#[cfg(feature = "render")]
1286pub fn setup_primary_egui_context_system(
1287    mut commands: Commands,
1288    new_cameras: Query<(Entity, Option<&EguiContext>), Added<bevy_render::camera::Camera>>,
1289    #[cfg(feature = "accesskit_placeholder")] adapters: Option<
1290        NonSend<bevy_winit::accessibility::AccessKitAdapters>,
1291    >,
1292    #[cfg(feature = "accesskit_placeholder")] mut manage_accessibility_updates: ResMut<
1293        bevy_a11y::ManageAccessibilityUpdates,
1294    >,
1295    enable_multipass_for_primary_context: Option<Res<EnableMultipassForPrimaryContext>>,
1296    mut egui_context_exists: Local<bool>,
1297) -> Result {
1298    for (camera_entity, context) in new_cameras {
1299        if context.is_some() || *egui_context_exists {
1300            *egui_context_exists = true;
1301            return Ok(());
1302        }
1303
1304        let context = EguiContext::default();
1305        #[cfg(feature = "accesskit_placeholder")]
1306        if let Some(adapters) = &adapters {
1307            // TODO: before re-enabling accesskit support, move to another system to do this for every context.
1308            if adapters.get(&camera_entity).is_some() {
1309                context.ctx.enable_accesskit();
1310                **manage_accessibility_updates = false;
1311            }
1312        }
1313
1314        log::debug!("Creating a primary Egui context");
1315        // See the list of required components to check the full list of components we add.
1316        let mut camera_commands = commands.get_entity(camera_entity)?;
1317        camera_commands.insert(context).insert(PrimaryEguiContext);
1318        if enable_multipass_for_primary_context.is_some() {
1319            camera_commands.insert(EguiMultipassSchedule::new(EguiPrimaryContextPass));
1320        }
1321        *egui_context_exists = true;
1322    }
1323
1324    Ok(())
1325}
1326
1327#[cfg(all(feature = "manage_clipboard", not(target_os = "android")))]
1328impl EguiClipboard {
1329    /// Places the text onto the clipboard.
1330    pub fn set_text(&mut self, contents: &str) {
1331        self.set_text_impl(contents);
1332    }
1333
1334    /// Sets the internal buffer of clipboard contents.
1335    /// This buffer is used to remember the contents of the last "Paste" event.
1336    #[cfg(target_arch = "wasm32")]
1337    pub fn set_text_internal(&mut self, text: &str) {
1338        self.clipboard.set_text_internal(text);
1339    }
1340
1341    /// Gets clipboard text content. Returns [`None`] if clipboard provider is unavailable or returns an error.
1342    #[must_use]
1343    pub fn get_text(&mut self) -> Option<String> {
1344        self.get_text_impl()
1345    }
1346
1347    /// Places an image to the clipboard.
1348    pub fn set_image(&mut self, image: &egui::ColorImage) {
1349        self.set_image_impl(image);
1350    }
1351
1352    /// Receives a clipboard event sent by the `copy`/`cut`/`paste` listeners.
1353    #[cfg(target_arch = "wasm32")]
1354    pub fn try_receive_clipboard_event(&self) -> Option<web_clipboard::WebClipboardEvent> {
1355        self.clipboard.try_receive_clipboard_event()
1356    }
1357
1358    #[cfg(not(target_arch = "wasm32"))]
1359    fn set_text_impl(&mut self, contents: &str) {
1360        if let Some(mut clipboard) = self.get() {
1361            if let Err(err) = clipboard.set_text(contents.to_owned()) {
1362                log::error!("Failed to set clipboard contents: {:?}", err);
1363            }
1364        }
1365    }
1366
1367    #[cfg(target_arch = "wasm32")]
1368    fn set_text_impl(&mut self, contents: &str) {
1369        self.clipboard.set_text(contents);
1370    }
1371
1372    #[cfg(not(target_arch = "wasm32"))]
1373    fn get_text_impl(&mut self) -> Option<String> {
1374        if let Some(mut clipboard) = self.get() {
1375            match clipboard.get_text() {
1376                Ok(contents) => return Some(contents),
1377                // We don't want to spam with this error as it usually means that the clipboard is either empty or has an incompatible format (e.g. image).
1378                Err(arboard::Error::ContentNotAvailable) => return Some("".to_string()),
1379                Err(err) => log::error!("Failed to get clipboard contents: {:?}", err),
1380            }
1381        };
1382        None
1383    }
1384
1385    #[cfg(target_arch = "wasm32")]
1386    #[allow(clippy::unnecessary_wraps)]
1387    fn get_text_impl(&mut self) -> Option<String> {
1388        self.clipboard.get_text()
1389    }
1390
1391    #[cfg(not(target_arch = "wasm32"))]
1392    fn set_image_impl(&mut self, image: &egui::ColorImage) {
1393        if let Some(mut clipboard) = self.get() {
1394            if let Err(err) = clipboard.set_image(arboard::ImageData {
1395                width: image.width(),
1396                height: image.height(),
1397                bytes: std::borrow::Cow::Borrowed(bytemuck::cast_slice(&image.pixels)),
1398            }) {
1399                log::error!("Failed to set clipboard contents: {:?}", err);
1400            }
1401        }
1402    }
1403
1404    #[cfg(target_arch = "wasm32")]
1405    fn set_image_impl(&mut self, image: &egui::ColorImage) {
1406        self.clipboard.set_image(image);
1407    }
1408
1409    #[cfg(not(target_arch = "wasm32"))]
1410    fn get(&self) -> Option<RefMut<Clipboard>> {
1411        self.clipboard
1412            .get_or(|| {
1413                Clipboard::new()
1414                    .map(RefCell::new)
1415                    .map_err(|err| {
1416                        log::error!("Failed to initialize clipboard: {:?}", err);
1417                    })
1418                    .ok()
1419            })
1420            .as_ref()
1421            .map(|cell| cell.borrow_mut())
1422    }
1423}
1424
1425/// The ordering value used for [`bevy_picking`].
1426#[cfg(feature = "picking")]
1427pub const PICKING_ORDER: f32 = 1_000_000.0;
1428
1429/// Captures pointers on Egui windows for [`bevy_picking`].
1430#[cfg(feature = "picking")]
1431pub fn capture_pointer_input_system(
1432    pointers: Query<(&PointerId, &PointerLocation)>,
1433    mut egui_context: Query<(
1434        Entity,
1435        &mut EguiContext,
1436        &EguiContextSettings,
1437        &bevy_render::camera::Camera,
1438    )>,
1439    mut output: EventWriter<PointerHits>,
1440    window_to_egui_context_map: Res<WindowToEguiContextMap>,
1441) {
1442    use helpers::QueryHelper;
1443
1444    for (pointer, location) in pointers
1445        .iter()
1446        .filter_map(|(i, p)| p.location.as_ref().map(|l| (i, l)))
1447    {
1448        if let NormalizedRenderTarget::Window(window) = location.target {
1449            for window_context_entity in window_to_egui_context_map
1450                .window_to_contexts
1451                .get(&window.entity())
1452                .cloned()
1453                .unwrap_or_default()
1454            {
1455                let Some((entity, mut ctx, settings, camera)) =
1456                    egui_context.get_some_mut(window_context_entity)
1457                else {
1458                    continue;
1459                };
1460                if !camera
1461                    .physical_viewport_rect()
1462                    .is_some_and(|rect| rect.as_rect().contains(location.position))
1463                {
1464                    continue;
1465                }
1466
1467                if settings.capture_pointer_input && ctx.get_mut().wants_pointer_input() {
1468                    let entry = (entity, HitData::new(entity, 0.0, None, None));
1469                    output.write(PointerHits::new(
1470                        *pointer,
1471                        Vec::from([entry]),
1472                        PICKING_ORDER,
1473                    ));
1474                }
1475            }
1476        }
1477    }
1478}
1479
1480/// Updates textures painted by Egui.
1481#[cfg(feature = "render")]
1482pub fn update_egui_textures_system(
1483    mut egui_render_output: Query<(Entity, &EguiRenderOutput)>,
1484    mut egui_managed_textures: ResMut<EguiManagedTextures>,
1485    mut image_assets: ResMut<Assets<Image>>,
1486) {
1487    for (entity, egui_render_output) in egui_render_output.iter_mut() {
1488        for (texture_id, image_delta) in &egui_render_output.textures_delta.set {
1489            let color_image = render::as_color_image(&image_delta.image);
1490
1491            let texture_id = match texture_id {
1492                egui::TextureId::Managed(texture_id) => *texture_id,
1493                egui::TextureId::User(_) => continue,
1494            };
1495
1496            let sampler = ImageSampler::Descriptor(render::texture_options_as_sampler_descriptor(
1497                &image_delta.options,
1498            ));
1499            if let Some(pos) = image_delta.pos {
1500                // Partial update.
1501                if let Some(managed_texture) = egui_managed_textures.get_mut(&(entity, texture_id))
1502                {
1503                    // TODO: when bevy supports it, only update the part of the texture that changes.
1504                    update_image_rect(&mut managed_texture.color_image, pos, &color_image);
1505                    let image =
1506                        render::color_image_as_bevy_image(&managed_texture.color_image, sampler);
1507                    managed_texture.handle = image_assets.add(image);
1508                } else {
1509                    log::warn!("Partial update of a missing texture (id: {:?})", texture_id);
1510                }
1511            } else {
1512                // Full update.
1513                let image = render::color_image_as_bevy_image(&color_image, sampler);
1514                let handle = image_assets.add(image);
1515                egui_managed_textures.insert(
1516                    (entity, texture_id),
1517                    EguiManagedTexture {
1518                        handle,
1519                        color_image,
1520                    },
1521                );
1522            }
1523        }
1524    }
1525
1526    fn update_image_rect(dest: &mut egui::ColorImage, [x, y]: [usize; 2], src: &egui::ColorImage) {
1527        for sy in 0..src.height() {
1528            for sx in 0..src.width() {
1529                dest[(x + sx, y + sy)] = src[(sx, sy)];
1530            }
1531        }
1532    }
1533}
1534
1535/// This system is responsible for deleting image assets of freed Egui-managed textures and deleting Egui user textures of removed Bevy image assets.
1536///
1537/// If you add textures via [`EguiContexts::add_image`] or [`EguiUserTextures::add_image`] by passing a weak handle,
1538/// the systems ensures that corresponding Egui textures are cleaned up as well.
1539#[cfg(feature = "render")]
1540pub fn free_egui_textures_system(
1541    mut egui_user_textures: ResMut<EguiUserTextures>,
1542    egui_render_output: Query<(Entity, &EguiRenderOutput)>,
1543    mut egui_managed_textures: ResMut<EguiManagedTextures>,
1544    mut image_assets: ResMut<Assets<Image>>,
1545    mut image_events: EventReader<AssetEvent<Image>>,
1546) {
1547    for (entity, egui_render_output) in egui_render_output.iter() {
1548        for &texture_id in &egui_render_output.textures_delta.free {
1549            if let egui::TextureId::Managed(texture_id) = texture_id {
1550                let managed_texture = egui_managed_textures.remove(&(entity, texture_id));
1551                if let Some(managed_texture) = managed_texture {
1552                    image_assets.remove(&managed_texture.handle);
1553                }
1554            }
1555        }
1556    }
1557
1558    for image_event in image_events.read() {
1559        if let AssetEvent::Removed { id } = image_event {
1560            egui_user_textures.remove_image(&Handle::<Image>::Weak(*id));
1561        }
1562    }
1563}
1564
1565/// Helper function for outputting a String from a JsValue
1566#[cfg(target_arch = "wasm32")]
1567pub fn string_from_js_value(value: &JsValue) -> String {
1568    value.as_string().unwrap_or_else(|| format!("{value:#?}"))
1569}
1570
1571#[cfg(target_arch = "wasm32")]
1572struct EventClosure<T> {
1573    target: web_sys::EventTarget,
1574    event_name: String,
1575    closure: wasm_bindgen::closure::Closure<dyn FnMut(T)>,
1576}
1577
1578/// Stores event listeners.
1579#[cfg(target_arch = "wasm32")]
1580#[derive(Default)]
1581pub struct SubscribedEvents {
1582    #[cfg(feature = "manage_clipboard")]
1583    clipboard_event_closures: Vec<EventClosure<web_sys::ClipboardEvent>>,
1584    composition_event_closures: Vec<EventClosure<web_sys::CompositionEvent>>,
1585    keyboard_event_closures: Vec<EventClosure<web_sys::KeyboardEvent>>,
1586    input_event_closures: Vec<EventClosure<web_sys::InputEvent>>,
1587    touch_event_closures: Vec<EventClosure<web_sys::TouchEvent>>,
1588}
1589
1590#[cfg(target_arch = "wasm32")]
1591impl SubscribedEvents {
1592    /// Use this method to unsubscribe from all stored events, this can be useful
1593    /// for gracefully destroying a Bevy instance in a page.
1594    pub fn unsubscribe_from_all_events(&mut self) {
1595        #[cfg(feature = "manage_clipboard")]
1596        Self::unsubscribe_from_events(&mut self.clipboard_event_closures);
1597        Self::unsubscribe_from_events(&mut self.composition_event_closures);
1598        Self::unsubscribe_from_events(&mut self.keyboard_event_closures);
1599        Self::unsubscribe_from_events(&mut self.input_event_closures);
1600        Self::unsubscribe_from_events(&mut self.touch_event_closures);
1601    }
1602
1603    fn unsubscribe_from_events<T>(events: &mut Vec<EventClosure<T>>) {
1604        let events_to_unsubscribe = std::mem::take(events);
1605
1606        if !events_to_unsubscribe.is_empty() {
1607            for event in events_to_unsubscribe {
1608                if let Err(err) = event.target.remove_event_listener_with_callback(
1609                    event.event_name.as_str(),
1610                    event.closure.as_ref().unchecked_ref(),
1611                ) {
1612                    log::error!(
1613                        "Failed to unsubscribe from event: {}",
1614                        string_from_js_value(&err)
1615                    );
1616                }
1617            }
1618        }
1619    }
1620}
1621
1622#[derive(QueryData)]
1623#[query_data(mutable)]
1624#[allow(missing_docs)]
1625#[cfg(feature = "render")]
1626pub struct UpdateUiSizeAndScaleQuery {
1627    ctx: &'static mut EguiContext,
1628    egui_input: &'static mut EguiInput,
1629    egui_settings: &'static EguiContextSettings,
1630    camera: &'static bevy_render::camera::Camera,
1631}
1632
1633#[cfg(feature = "render")]
1634/// Updates UI [`egui::RawInput::screen_rect`] and calls [`egui::Context::set_pixels_per_point`].
1635pub fn update_ui_size_and_scale_system(mut contexts: Query<UpdateUiSizeAndScaleQuery>) {
1636    for mut context in contexts.iter_mut() {
1637        let Some((scale_factor, viewport_rect)) = context
1638            .camera
1639            .target_scaling_factor()
1640            .map(|scale_factor| scale_factor * context.egui_settings.scale_factor)
1641            .zip(context.camera.physical_viewport_rect())
1642        else {
1643            continue;
1644        };
1645
1646        let viewport_rect = egui::Rect {
1647            min: helpers::vec2_into_egui_pos2(viewport_rect.min.as_vec2() / scale_factor),
1648            max: helpers::vec2_into_egui_pos2(viewport_rect.max.as_vec2() / scale_factor),
1649        };
1650        if viewport_rect.width() < 1.0 || viewport_rect.height() < 1.0 {
1651            continue;
1652        }
1653        context.egui_input.screen_rect = Some(viewport_rect);
1654        context.ctx.get_mut().set_pixels_per_point(scale_factor);
1655    }
1656}
1657
1658/// Marks a pass start for Egui.
1659pub fn begin_pass_system(
1660    mut contexts: Query<
1661        (&mut EguiContext, &EguiContextSettings, &mut EguiInput),
1662        Without<EguiMultipassSchedule>,
1663    >,
1664) {
1665    for (mut ctx, egui_settings, mut egui_input) in contexts.iter_mut() {
1666        if !egui_settings.run_manually {
1667            ctx.get_mut().begin_pass(egui_input.take());
1668        }
1669    }
1670}
1671
1672/// Marks a pass end for Egui.
1673pub fn end_pass_system(
1674    mut contexts: Query<
1675        (&mut EguiContext, &EguiContextSettings, &mut EguiFullOutput),
1676        Without<EguiMultipassSchedule>,
1677    >,
1678) {
1679    for (mut ctx, egui_settings, mut full_output) in contexts.iter_mut() {
1680        if !egui_settings.run_manually {
1681            **full_output = Some(ctx.get_mut().end_pass());
1682        }
1683    }
1684}
1685
1686/// Updates the states of [`ManageAccessibilityUpdates`] and [`AccessKitAdapters`].
1687#[cfg(feature = "accesskit_placeholder")]
1688pub fn update_accessibility_system(
1689    requested: Res<bevy_a11y::AccessibilityRequested>,
1690    mut manage_accessibility_updates: ResMut<bevy_a11y::ManageAccessibilityUpdates>,
1691    outputs: Query<(Entity, &EguiOutput)>,
1692    mut adapters: NonSendMut<bevy_winit::accessibility::AccessKitAdapters>,
1693) {
1694    if requested.get() {
1695        for (entity, output) in &outputs {
1696            if let Some(adapter) = adapters.get_mut(&entity) {
1697                if let Some(update) = &output.platform_output.accesskit_update {
1698                    **manage_accessibility_updates = false;
1699                    adapter.update_if_active(|| update.clone());
1700                } else if !**manage_accessibility_updates {
1701                    **manage_accessibility_updates = true;
1702                }
1703            }
1704        }
1705    }
1706}
1707
1708#[derive(QueryData)]
1709#[query_data(mutable)]
1710#[allow(missing_docs)]
1711pub struct MultiPassEguiQuery {
1712    entity: Entity,
1713    context: &'static mut EguiContext,
1714    input: &'static mut EguiInput,
1715    output: &'static mut EguiFullOutput,
1716    multipass_schedule: &'static EguiMultipassSchedule,
1717    settings: &'static EguiContextSettings,
1718}
1719
1720/// Runs Egui contexts with the [`EguiMultipassSchedule`] component. If there are no contexts with
1721/// this component, runs the [`EguiPrimaryContextPass`] schedule once independently.
1722pub fn run_egui_context_pass_loop_system(world: &mut World) {
1723    let mut contexts_query = world.query::<MultiPassEguiQuery>();
1724    let mut used_schedules = HashSet::<InternedScheduleLabel>::default();
1725
1726    let mut multipass_contexts: Vec<_> = contexts_query
1727        .iter_mut(world)
1728        .filter_map(|mut egui_context| {
1729            if egui_context.settings.run_manually {
1730                return None;
1731            }
1732
1733            Some((
1734                egui_context.entity,
1735                egui_context.context.get_mut().clone(),
1736                egui_context.input.take(),
1737                egui_context.multipass_schedule.clone(),
1738            ))
1739        })
1740        .collect();
1741
1742    for (entity, ctx, ref mut input, EguiMultipassSchedule(multipass_schedule)) in
1743        &mut multipass_contexts
1744    {
1745        if !used_schedules.insert(*multipass_schedule) {
1746            panic!("Each Egui context running in the multi-pass mode must have a unique schedule (attempted to reuse schedule {multipass_schedule:?})");
1747        }
1748
1749        let output = ctx.run(input.take(), |_| {
1750            let _ = world.try_run_schedule(*multipass_schedule);
1751        });
1752
1753        **contexts_query
1754            .get_mut(world, *entity)
1755            .expect("previously queried context")
1756            .output = Some(output);
1757    }
1758
1759    // If Egui's running in the single-pass mode and a user placed all the UI systems in `EguiContextPass`,
1760    // we want to run the schedule just once.
1761    // (And since the code above runs only for multi-pass contexts, it's not run yet in the case of single-pass.)
1762    if world
1763        .query_filtered::<Entity, (With<EguiContext>, With<PrimaryEguiContext>)>()
1764        .iter(world)
1765        .next()
1766        .is_none()
1767    {
1768        // Silly control flow to test that we still have a context. Attempting to run the schedule
1769        // when a user has closed a window will result in a panic.
1770        return;
1771    }
1772    if !used_schedules.contains(&ScheduleLabel::intern(&EguiPrimaryContextPass)) {
1773        let _ = world.try_run_schedule(EguiPrimaryContextPass);
1774    }
1775}
1776
1777/// Extension for the [`EntityCommands`] trait.
1778#[cfg(feature = "picking")]
1779pub trait BevyEguiEntityCommandsExt {
1780    /// Makes an entity [`bevy_picking::Pickable`] and adds observers to react to pointer events by linking them with an Egui context.
1781    fn add_picking_observers_for_context(&mut self, context: Entity) -> &mut Self;
1782}
1783
1784#[cfg(feature = "picking")]
1785impl<'a> BevyEguiEntityCommandsExt for EntityCommands<'a> {
1786    fn add_picking_observers_for_context(&mut self, context: Entity) -> &mut Self {
1787        self.insert(picking::PickableEguiContext(context))
1788            .observe(picking::handle_over_system)
1789            .observe(picking::handle_out_system)
1790            .observe(picking::handle_move_system)
1791    }
1792}
1793
1794#[cfg(test)]
1795mod tests {
1796    #[test]
1797    fn test_readme_deps() {
1798        version_sync::assert_markdown_deps_updated!("README.md");
1799    }
1800}