1#![warn(missing_docs)]
2#![allow(clippy::type_complexity)]
3
4pub mod helpers;
151pub mod input;
153pub mod output;
155#[cfg(feature = "picking")]
157pub mod picking;
158#[cfg(feature = "render")]
160pub mod render;
161#[cfg(target_arch = "wasm32")]
163pub mod text_agent;
164#[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
224pub struct EguiPlugin {
226 #[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 #[cfg(feature = "bevy_ui")]
362 pub ui_render_order: UiRenderOrder,
363
364 #[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#[cfg(feature = "bevy_ui")]
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391pub enum UiRenderOrder {
392 EguiAboveBevyUi,
394 BevyUiAboveEgui,
396}
397
398#[derive(Clone, Debug, Resource, Reflect)]
400pub struct EguiGlobalSettings {
401 pub auto_create_primary_context: bool,
405 pub enable_focused_non_window_context_updates: bool,
410 pub input_system_settings: EguiInputSystemSettings,
412 pub enable_absorb_bevy_input_system: bool,
426 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#[derive(Resource)]
447pub struct EnableMultipassForPrimaryContext;
448
449#[derive(Clone, Debug, Component, Reflect)]
451pub struct EguiContextSettings {
452 pub run_manually: bool,
454 pub scale_factor: f32,
468 #[cfg(feature = "open_url")]
471 pub default_open_url_target: Option<String>,
472 #[cfg(feature = "picking")]
474 pub capture_pointer_input: bool,
475 pub input_system_settings: EguiInputSystemSettings,
477 pub enable_cursor_icon_updates: bool,
482}
483
484impl 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)]
511pub struct EguiInputSystemSettings {
513 pub run_write_modifiers_keys_state_system: bool,
515 pub run_write_window_pointer_moved_messages_system: bool,
517 pub run_write_pointer_button_messages_system: bool,
519 pub run_write_window_touch_messages_system: bool,
521 pub run_write_non_window_pointer_moved_messages_system: bool,
523 pub run_write_mouse_wheel_messages_system: bool,
525 pub run_write_non_window_touch_messages_system: bool,
527 pub run_write_keyboard_input_messages_system: bool,
529 pub run_write_ime_messages_system: bool,
531 pub run_write_file_dnd_messages_system: bool,
533 #[cfg(target_arch = "wasm32")]
535 pub run_write_text_agent_channel_messages_system: bool,
536 #[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#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
565pub struct EguiPrimaryContextPass;
566
567#[derive(Component, Clone)]
569#[require(EguiMultipassSchedule::new(EguiPrimaryContextPass))]
570pub struct PrimaryEguiContext;
571
572#[derive(Component, Clone)]
575#[require(EguiContext)]
576pub struct EguiMultipassSchedule(pub InternedScheduleLabel);
577
578impl EguiMultipassSchedule {
579 pub fn new(schedule: impl ScheduleLabel) -> Self {
581 Self(schedule.intern())
582 }
583}
584
585#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
589pub struct EguiInput(pub egui::RawInput);
590
591#[derive(Component, Clone, Default, Deref, DerefMut)]
593pub struct EguiFullOutput(pub Option<egui::FullOutput>);
594
595#[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#[derive(Component, Clone, Default, Debug)]
609pub struct EguiRenderOutput {
610 pub paint_jobs: Vec<egui::ClippedPrimitive>,
615 pub textures_delta: egui::TexturesDelta,
617}
618
619impl EguiRenderOutput {
620 pub fn is_empty(&self) -> bool {
622 self.paint_jobs.is_empty() && self.textures_delta.is_empty()
623 }
624}
625
626#[derive(Component, Clone, Default)]
628pub struct EguiOutput {
629 pub platform_output: egui::PlatformOutput,
631}
632
633#[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 #[cfg(feature = "immutable_ctx")]
661 #[must_use]
662 pub fn get(&self) -> &egui::Context {
663 &self.ctx
664 }
665
666 #[must_use]
676 pub fn get_mut(&mut self) -> &mut egui::Context {
677 &mut self.ctx
678 }
679}
680
681type 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)]
695pub 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 #[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 #[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 #[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 #[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 #[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 #[cfg(feature = "render")]
799 pub fn add_image(&mut self, image: EguiTextureHandle) -> egui::TextureId {
800 self.user_textures.add_image(image)
801 }
802
803 #[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 #[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#[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 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 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 #[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#[derive(Clone, Debug)]
886pub enum EguiTextureHandle {
887 Strong(Handle<Image>),
892 Weak(AssetId<Image>),
894}
895
896#[cfg(feature = "render")]
897impl EguiTextureHandle {
898 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#[derive(Component, Debug, Default, Clone, Copy, PartialEq)]
917pub struct RenderComputedScaleFactor {
918 pub scale_factor: f32,
920}
921
922pub mod node {
924 pub const EGUI_PASS: &str = "egui_pass";
926}
927
928#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
929pub enum EguiStartupSet {
931 InitContexts,
933}
934
935#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
937pub enum EguiPreUpdateSet {
938 InitContexts,
940 ProcessInput,
946 BeginPass,
948}
949
950#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
952pub enum EguiInputSet {
953 InitReading,
957 FocusContext,
959 ReadBevyMessages,
961 WriteEguiEvents,
963}
964
965#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)]
967pub enum EguiPostUpdateSet {
968 EndPass,
970 ProcessOutput,
972 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 #[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 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 #[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 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 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 #[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 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#[cfg(feature = "render")]
1417#[derive(Resource, Deref, DerefMut, Default)]
1418pub struct EguiManagedTextures(pub HashMap<(Entity, u64), EguiManagedTexture>);
1419
1420#[cfg(feature = "render")]
1422pub struct EguiManagedTexture {
1423 pub handle: Handle<Image>,
1425 pub color_image: egui::ColorImage,
1427}
1428
1429pub 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 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 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 pub fn set_text(&mut self, contents: &str) {
1478 self.set_text_impl(contents);
1479 }
1480
1481 #[cfg(target_arch = "wasm32")]
1484 pub fn set_text_internal(&mut self, text: &str) {
1485 self.clipboard.set_text_internal(text);
1486 }
1487
1488 #[must_use]
1490 pub fn get_text(&mut self) -> Option<String> {
1491 self.get_text_impl()
1492 }
1493
1494 pub fn set_image(&mut self, image: &egui::ColorImage) {
1496 self.set_image_impl(image);
1497 }
1498
1499 #[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 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#[cfg(feature = "picking")]
1574pub const PICKING_ORDER: f32 = 1_000_000.0;
1575
1576#[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#[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 if let Some(managed_texture) = egui_managed_textures.get_mut(&(entity, texture_id))
1649 {
1650 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 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#[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#[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#[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 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
1779pub 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
1803pub 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
1817pub 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#[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
1865pub 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 world
1908 .query_filtered::<Entity, (With<EguiContext>, With<PrimaryEguiContext>)>()
1909 .iter(world)
1910 .next()
1911 .is_none()
1912 {
1913 return;
1916 }
1917 if !used_schedules.contains(&ScheduleLabel::intern(&EguiPrimaryContextPass)) {
1918 let _ = world.try_run_schedule(EguiPrimaryContextPass);
1919 }
1920}
1921
1922#[cfg(feature = "picking")]
1924pub trait BevyEguiEntityCommandsExt {
1925 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}