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