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