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