1#[cfg(target_arch = "wasm32")]
2use crate::text_agent::{is_mobile_safari, update_text_agent};
3use crate::{
4 helpers::{vec2_into_egui_pos2, QueryHelper},
5 EguiContext, EguiContextSettings, EguiGlobalSettings, EguiInput, EguiOutput,
6};
7use bevy_ecs::{event::EventIterator, prelude::*, system::SystemParam};
8use bevy_input::{
9 keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput},
10 mouse::{MouseButton, MouseButtonInput, MouseScrollUnit, MouseWheel},
11 touch::TouchInput,
12 ButtonInput, ButtonState,
13};
14use bevy_log::{self as log};
15use bevy_time::{Real, Time};
16use bevy_window::{CursorMoved, FileDragAndDrop, Ime, Window};
17use egui::Modifiers;
18
19#[derive(Component, Default)]
21pub struct EguiContextPointerPosition {
22 pub position: egui::Pos2,
24}
25
26#[derive(Component, Default)]
28pub struct EguiContextPointerTouchId {
29 pub pointer_touch_id: Option<u64>,
31}
32
33#[derive(Component, Default)]
35pub struct EguiContextImeState {
36 pub has_sent_ime_enabled: bool,
38 pub is_ime_allowed: bool,
40}
41
42#[derive(Event)]
43pub struct EguiInputEvent {
45 pub context: Entity,
47 pub event: egui::Event,
49}
50
51#[derive(Event)]
52pub struct EguiFileDragAndDropEvent {
54 pub context: Entity,
56 pub event: FileDragAndDrop,
58}
59
60#[derive(Resource, Clone)]
61pub struct HoveredNonWindowEguiContext(pub Entity);
67
68#[derive(Resource, Clone)]
82pub struct FocusedNonWindowEguiContext(pub Entity);
83
84#[derive(Resource, Clone, Copy, Debug)]
86pub struct ModifierKeysState {
87 pub shift: bool,
89 pub ctrl: bool,
91 pub alt: bool,
93 pub win: bool,
95 is_macos: bool,
96}
97
98impl Default for ModifierKeysState {
99 fn default() -> Self {
100 let mut state = Self {
101 shift: false,
102 ctrl: false,
103 alt: false,
104 win: false,
105 is_macos: false,
106 };
107
108 #[cfg(not(target_arch = "wasm32"))]
109 {
110 state.is_macos = cfg!(target_os = "macos");
111 }
112
113 #[cfg(target_arch = "wasm32")]
114 if let Some(window) = web_sys::window() {
115 let nav = window.navigator();
116 if let Ok(user_agent) = nav.user_agent() {
117 if user_agent.to_ascii_lowercase().contains("mac") {
118 state.is_macos = true;
119 }
120 }
121 }
122
123 state
124 }
125}
126
127impl ModifierKeysState {
128 pub fn to_egui_modifiers(&self) -> egui::Modifiers {
130 egui::Modifiers {
131 alt: self.alt,
132 ctrl: self.ctrl,
133 shift: self.shift,
134 mac_cmd: if self.is_macos { self.win } else { false },
135 command: if self.is_macos { self.win } else { self.ctrl },
136 }
137 }
138
139 pub fn text_input_is_allowed(&self) -> bool {
141 !self.win && !self.ctrl || !self.is_macos && self.ctrl && self.alt
143 }
144
145 fn reset(&mut self) {
146 self.shift = false;
147 self.ctrl = false;
148 self.alt = false;
149 self.win = false;
150 }
151}
152
153#[derive(Resource, Default)]
154pub struct WindowToEguiContextMap {
157 pub window_to_contexts:
159 bevy_platform::collections::HashMap<Entity, bevy_platform::collections::HashSet<Entity>>,
160 pub context_to_window: bevy_platform::collections::HashMap<Entity, Entity>,
162}
163
164#[cfg(feature = "render")]
165impl WindowToEguiContextMap {
166 pub fn on_egui_context_added_system(
168 mut res: ResMut<Self>,
169 added_contexts: Query<(Entity, &bevy_render::camera::Camera), Added<EguiContext>>,
170 primary_window: Query<Entity, With<bevy_window::PrimaryWindow>>,
171 ) {
172 for (egui_context_entity, camera) in added_contexts {
173 if let Some(bevy_render::camera::NormalizedRenderTarget::Window(window_ref)) =
174 camera.target.normalize(primary_window.single().ok())
175 {
176 res.window_to_contexts
177 .entry(window_ref.entity())
178 .or_default()
179 .insert(egui_context_entity);
180 res.context_to_window
181 .insert(egui_context_entity, window_ref.entity());
182 }
183 }
184 }
185
186 pub fn on_egui_context_removed_system(
188 mut res: ResMut<Self>,
189 mut removed_contexts: RemovedComponents<EguiContext>,
190 ) {
191 for egui_context_entity in removed_contexts.read() {
192 let Some(window_entity) = res.context_to_window.remove(&egui_context_entity) else {
193 continue;
194 };
195
196 let Some(window_contexts) = res.window_to_contexts.get_mut(&window_entity) else {
197 log::warn!(
198 "A destroyed Egui context's window isn't registered: {egui_context_entity:?}"
199 );
200 continue;
201 };
202
203 window_contexts.remove(&egui_context_entity);
204 }
205 }
206}
207
208pub struct EguiContextsEventIterator<'a, E: Event, F> {
210 event_iter: EventIterator<'a, E>,
211 map_event_to_window_id_f: F,
212 current_event: Option<&'a E>,
213 current_event_contexts: Vec<Entity>,
214 non_window_context: Option<Entity>,
215 map: &'a WindowToEguiContextMap,
216}
217
218impl<'a, E: Event, F: FnMut(&'a E) -> Entity> Iterator for EguiContextsEventIterator<'a, E, F> {
219 type Item = (&'a E, Entity);
220
221 fn next(&mut self) -> Option<Self::Item> {
222 if self.current_event_contexts.is_empty() {
223 self.current_event = None;
224 }
225
226 if self.current_event.is_none() {
227 self.current_event = self.event_iter.next();
228
229 if self.non_window_context.is_some() {
230 return self.current_event.zip(self.non_window_context);
231 }
232
233 if let Some(current) = self.current_event {
234 if let Some(contexts) = self
235 .map
236 .window_to_contexts
237 .get(&(self.map_event_to_window_id_f)(current))
238 {
239 self.current_event_contexts = contexts.iter().cloned().collect();
240 }
241 }
242 }
243
244 self.current_event.zip(self.current_event_contexts.pop())
245 }
246}
247
248#[derive(SystemParam)]
249pub struct EguiContextEventReader<'w, 's, E: Event> {
251 event_reader: EventReader<'w, 's, E>,
252 map: Res<'w, WindowToEguiContextMap>,
253 hovered_non_window_egui_context: Option<Res<'w, HoveredNonWindowEguiContext>>,
254 focused_non_window_egui_context: Option<Res<'w, FocusedNonWindowEguiContext>>,
255}
256
257impl<'w, 's, E: Event> EguiContextEventReader<'w, 's, E> {
258 pub fn read<'a, F>(
261 &'a mut self,
262 map_event_to_window_id_f: F,
263 ) -> EguiContextsEventIterator<'a, E, F>
264 where
265 F: FnMut(&'a E) -> Entity,
266 E: Event,
267 {
268 EguiContextsEventIterator {
269 event_iter: self.event_reader.read(),
270 map_event_to_window_id_f,
271 current_event: None,
272 current_event_contexts: Vec::new(),
273 non_window_context: None,
274 map: &self.map,
275 }
276 }
277
278 pub fn read_with_non_window_hovered<'a, F>(
280 &'a mut self,
281 map_event_to_window_id_f: F,
282 ) -> EguiContextsEventIterator<'a, E, F>
283 where
284 F: FnMut(&'a E) -> Entity,
285 E: Event,
286 {
287 EguiContextsEventIterator {
288 event_iter: self.event_reader.read(),
289 map_event_to_window_id_f,
290 current_event: None,
291 current_event_contexts: Vec::new(),
292 non_window_context: self
293 .hovered_non_window_egui_context
294 .as_deref()
295 .map(|context| context.0),
296 map: &self.map,
297 }
298 }
299
300 pub fn read_with_non_window_focused<'a, F>(
302 &'a mut self,
303 map_event_to_window_id_f: F,
304 ) -> EguiContextsEventIterator<'a, E, F>
305 where
306 F: FnMut(&'a E) -> Entity,
307 E: Event,
308 {
309 EguiContextsEventIterator {
310 event_iter: self.event_reader.read(),
311 map_event_to_window_id_f,
312 current_event: None,
313 current_event_contexts: Vec::new(),
314 non_window_context: self
315 .focused_non_window_egui_context
316 .as_deref()
317 .map(|context| context.0),
318 map: &self.map,
319 }
320 }
321}
322
323pub fn write_modifiers_keys_state_system(
325 mut ev_keyboard_input: EventReader<KeyboardInput>,
326 mut ev_focus: EventReader<KeyboardFocusLost>,
327 mut modifier_keys_state: ResMut<ModifierKeysState>,
328) {
329 if !ev_focus.is_empty() {
331 ev_focus.clear();
332 modifier_keys_state.reset();
333 }
334
335 for event in ev_keyboard_input.read() {
336 let KeyboardInput {
337 logical_key, state, ..
338 } = event;
339 match logical_key {
340 Key::Shift => {
341 modifier_keys_state.shift = state.is_pressed();
342 }
343 Key::Control => {
344 modifier_keys_state.ctrl = state.is_pressed();
345 }
346 Key::Alt => {
347 modifier_keys_state.alt = state.is_pressed();
348 }
349 Key::Super | Key::Meta => {
350 modifier_keys_state.win = state.is_pressed();
351 }
352 _ => {}
353 };
354 }
355}
356
357pub fn write_window_pointer_moved_events_system(
359 mut cursor_moved_reader: EguiContextEventReader<CursorMoved>,
360 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
361 mut egui_contexts: Query<
362 (&EguiContextSettings, &mut EguiContextPointerPosition),
363 With<EguiContext>,
364 >,
365) {
366 for (event, context) in cursor_moved_reader.read(|event| event.window) {
367 let Some((context_settings, mut context_pointer_position)) =
368 egui_contexts.get_some_mut(context)
369 else {
370 continue;
371 };
372
373 if !context_settings
374 .input_system_settings
375 .run_write_window_pointer_moved_events_system
376 {
377 continue;
378 }
379
380 let scale_factor = context_settings.scale_factor;
381 let pointer_position = vec2_into_egui_pos2(event.position / scale_factor);
382 context_pointer_position.position = pointer_position;
383 egui_input_event_writer.write(EguiInputEvent {
384 context,
385 event: egui::Event::PointerMoved(pointer_position),
386 });
387 }
388}
389
390pub fn write_pointer_button_events_system(
393 egui_global_settings: Res<EguiGlobalSettings>,
394 mut commands: Commands,
395 modifier_keys_state: Res<ModifierKeysState>,
396 mut mouse_button_input_reader: EguiContextEventReader<MouseButtonInput>,
397 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
398 egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
399) {
400 let modifiers = modifier_keys_state.to_egui_modifiers();
401 let hovered_non_window_egui_context = mouse_button_input_reader
402 .hovered_non_window_egui_context
403 .as_deref()
404 .cloned();
405 for (event, context) in
406 mouse_button_input_reader.read_with_non_window_hovered(|event| event.window)
407 {
408 let Some((context_settings, context_pointer_position)) = egui_contexts.get_some(context)
409 else {
410 continue;
411 };
412
413 if !context_settings
414 .input_system_settings
415 .run_write_pointer_button_events_system
416 {
417 continue;
418 }
419
420 let button = match event.button {
421 MouseButton::Left => Some(egui::PointerButton::Primary),
422 MouseButton::Right => Some(egui::PointerButton::Secondary),
423 MouseButton::Middle => Some(egui::PointerButton::Middle),
424 MouseButton::Back => Some(egui::PointerButton::Extra1),
425 MouseButton::Forward => Some(egui::PointerButton::Extra2),
426 _ => None,
427 };
428 let Some(button) = button else {
429 continue;
430 };
431 let pressed = match event.state {
432 ButtonState::Pressed => true,
433 ButtonState::Released => false,
434 };
435 egui_input_event_writer.write(EguiInputEvent {
436 context,
437 event: egui::Event::PointerButton {
438 pos: context_pointer_position.position,
439 button,
440 pressed,
441 modifiers,
442 },
443 });
444
445 if egui_global_settings.enable_focused_non_window_context_updates && pressed {
447 if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
448 commands.insert_resource(FocusedNonWindowEguiContext(
449 hovered_non_window_egui_context.0,
450 ));
451 } else {
452 commands.remove_resource::<FocusedNonWindowEguiContext>();
453 }
454 }
455 }
456}
457
458pub fn write_non_window_pointer_moved_events_system(
460 hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
461 mut cursor_moved_reader: EventReader<CursorMoved>,
462 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
463 egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
464) {
465 if cursor_moved_reader.is_empty() {
466 return;
467 }
468
469 cursor_moved_reader.clear();
470 let Some(HoveredNonWindowEguiContext(hovered_non_window_egui_context)) =
471 hovered_non_window_egui_context.as_deref()
472 else {
473 return;
474 };
475
476 let Some((context_settings, context_pointer_position)) =
477 egui_contexts.get_some(*hovered_non_window_egui_context)
478 else {
479 return;
480 };
481
482 if !context_settings
483 .input_system_settings
484 .run_write_non_window_pointer_moved_events_system
485 {
486 return;
487 }
488
489 egui_input_event_writer.write(EguiInputEvent {
490 context: *hovered_non_window_egui_context,
491 event: egui::Event::PointerMoved(context_pointer_position.position),
492 });
493}
494
495pub fn write_mouse_wheel_events_system(
497 modifier_keys_state: Res<ModifierKeysState>,
498 mut mouse_wheel_reader: EguiContextEventReader<MouseWheel>,
499 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
500 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
501) {
502 let modifiers = modifier_keys_state.to_egui_modifiers();
503 for (event, context) in mouse_wheel_reader.read_with_non_window_hovered(|event| event.window) {
504 let delta = egui::vec2(event.x, event.y);
505 let unit = match event.unit {
506 MouseScrollUnit::Line => egui::MouseWheelUnit::Line,
507 MouseScrollUnit::Pixel => egui::MouseWheelUnit::Point,
508 };
509
510 let Some(context_settings) = egui_contexts.get_some(context) else {
511 continue;
512 };
513
514 if !context_settings
515 .input_system_settings
516 .run_write_mouse_wheel_events_system
517 {
518 continue;
519 }
520
521 egui_input_event_writer.write(EguiInputEvent {
522 context,
523 event: egui::Event::MouseWheel {
524 unit,
525 delta,
526 modifiers,
527 },
528 });
529 }
530}
531
532pub fn write_keyboard_input_events_system(
534 modifier_keys_state: Res<ModifierKeysState>,
535 #[cfg(all(
536 feature = "manage_clipboard",
537 not(target_os = "android"),
538 not(target_arch = "wasm32")
539 ))]
540 mut egui_clipboard: ResMut<crate::EguiClipboard>,
541 mut keyboard_input_reader: EguiContextEventReader<KeyboardInput>,
542 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
543 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
544) {
545 let modifiers = modifier_keys_state.to_egui_modifiers();
546 for (event, context) in keyboard_input_reader.read_with_non_window_focused(|event| event.window)
547 {
548 let Some(context_settings) = egui_contexts.get_some(context) else {
549 continue;
550 };
551
552 if !context_settings
553 .input_system_settings
554 .run_write_keyboard_input_events_system
555 {
556 continue;
557 }
558
559 if modifier_keys_state.text_input_is_allowed() && event.state.is_pressed() {
560 match &event.logical_key {
561 Key::Character(char) if char.matches(char::is_control).count() == 0 => {
562 egui_input_event_writer.write(EguiInputEvent {
563 context,
564 event: egui::Event::Text(char.to_string()),
565 });
566 }
567 Key::Space => {
568 egui_input_event_writer.write(EguiInputEvent {
569 context,
570 event: egui::Event::Text(" ".to_string()),
571 });
572 }
573 _ => (),
574 }
575 }
576
577 let key = crate::helpers::bevy_to_egui_key(&event.logical_key);
578 let physical_key = crate::helpers::bevy_to_egui_physical_key(&event.key_code);
579
580 let (Some(key), physical_key) = (key.or(physical_key), physical_key) else {
583 continue;
584 };
585
586 let egui_event = egui::Event::Key {
587 key,
588 pressed: event.state.is_pressed(),
589 repeat: false,
590 modifiers,
591 physical_key,
592 };
593 egui_input_event_writer.write(EguiInputEvent {
594 context,
595 event: egui_event,
596 });
597
598 #[cfg(all(
601 feature = "manage_clipboard",
602 not(target_os = "android"),
603 not(target_arch = "wasm32")
604 ))]
605 if modifiers.command && event.state.is_pressed() {
606 match key {
607 egui::Key::C => {
608 egui_input_event_writer.write(EguiInputEvent {
609 context,
610 event: egui::Event::Copy,
611 });
612 }
613 egui::Key::X => {
614 egui_input_event_writer.write(EguiInputEvent {
615 context,
616 event: egui::Event::Cut,
617 });
618 }
619 egui::Key::V => {
620 if let Some(contents) = egui_clipboard.get_text() {
621 egui_input_event_writer.write(EguiInputEvent {
622 context,
623 event: egui::Event::Text(contents),
624 });
625 }
626 }
627 _ => {}
628 }
629 }
630 }
631}
632
633pub fn write_ime_events_system(
635 mut ime_reader: EguiContextEventReader<Ime>,
636 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
637 mut egui_contexts: Query<
638 (
639 Entity,
640 &EguiContextSettings,
641 &mut EguiContextImeState,
642 &EguiOutput,
643 ),
644 With<EguiContext>,
645 >,
646) {
647 for (event, context) in ime_reader.read_with_non_window_focused(|event| match &event {
648 Ime::Preedit { window, .. }
649 | Ime::Commit { window, .. }
650 | Ime::Disabled { window }
651 | Ime::Enabled { window } => *window,
652 }) {
653 let Some((_entity, context_settings, mut ime_state, _egui_output)) =
654 egui_contexts.get_some_mut(context)
655 else {
656 continue;
657 };
658
659 if !context_settings
660 .input_system_settings
661 .run_write_ime_events_system
662 {
663 continue;
664 }
665
666 let ime_event_enable =
667 |ime_state: &mut EguiContextImeState,
668 egui_input_event_writer: &mut EventWriter<EguiInputEvent>| {
669 if !ime_state.has_sent_ime_enabled {
670 egui_input_event_writer.write(EguiInputEvent {
671 context,
672 event: egui::Event::Ime(egui::ImeEvent::Enabled),
673 });
674 ime_state.has_sent_ime_enabled = true;
675 }
676 };
677
678 let ime_event_disable =
679 |ime_state: &mut EguiContextImeState,
680 egui_input_event_writer: &mut EventWriter<EguiInputEvent>| {
681 if !ime_state.has_sent_ime_enabled {
682 egui_input_event_writer.write(EguiInputEvent {
683 context,
684 event: egui::Event::Ime(egui::ImeEvent::Disabled),
685 });
686 ime_state.has_sent_ime_enabled = false;
687 }
688 };
689
690 match event {
692 Ime::Enabled { window: _ } => {
693 ime_event_enable(&mut ime_state, &mut egui_input_event_writer);
694 }
695 Ime::Preedit {
696 value,
697 window: _,
698 cursor: _,
699 } => {
700 ime_event_enable(&mut ime_state, &mut egui_input_event_writer);
701 egui_input_event_writer.write(EguiInputEvent {
702 context,
703 event: egui::Event::Ime(egui::ImeEvent::Preedit(value.clone())),
704 });
705 }
706 Ime::Commit { value, window: _ } => {
707 egui_input_event_writer.write(EguiInputEvent {
708 context,
709 event: egui::Event::Ime(egui::ImeEvent::Commit(value.clone())),
710 });
711 ime_event_disable(&mut ime_state, &mut egui_input_event_writer);
712 }
713 Ime::Disabled { window: _ } => {
714 ime_event_disable(&mut ime_state, &mut egui_input_event_writer);
715 }
716 }
717 }
718}
719
720#[cfg(any(target_os = "ios", target_os = "android"))]
723pub fn set_ime_allowed_system(
724 mut egui_context: Query<(&EguiOutput, &mut EguiContextImeState)>,
725 windows: Query<Entity, With<bevy_window::PrimaryWindow>>,
726 winit_windows: NonSendMut<bevy_winit::WinitWindows>,
727) {
728 let Ok(window) = windows.single() else {
730 return;
731 };
732
733 let Some(winit_window) = winit_windows.get_window(window) else {
734 log::warn!(
735 "Cannot access an underlying winit window for a window entity {}",
736 window
737 );
738
739 return;
740 };
741
742 let Ok((egui_output, mut egui_ime_state)) = egui_context.single_mut() else {
743 return;
744 };
745
746 let ime_allowed = egui_output.platform_output.ime.is_some();
747 if ime_allowed != egui_ime_state.is_ime_allowed {
748 winit_window.set_ime_allowed(ime_allowed);
749 egui_ime_state.is_ime_allowed = ime_allowed;
750 }
751}
752
753pub fn write_file_dnd_events_system(
755 mut dnd_reader: EguiContextEventReader<FileDragAndDrop>,
756 mut egui_file_dnd_event_writer: EventWriter<EguiFileDragAndDropEvent>,
757 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
758) {
759 for (event, context) in dnd_reader.read_with_non_window_hovered(|event| match &event {
760 FileDragAndDrop::DroppedFile { window, .. }
761 | FileDragAndDrop::HoveredFile { window, .. }
762 | FileDragAndDrop::HoveredFileCanceled { window } => *window,
763 }) {
764 let Some(context_settings) = egui_contexts.get_some(context) else {
765 continue;
766 };
767
768 if !context_settings
769 .input_system_settings
770 .run_write_file_dnd_events_system
771 {
772 continue;
773 }
774
775 match event {
776 FileDragAndDrop::DroppedFile { window, path_buf } => {
777 egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
778 context,
779 event: FileDragAndDrop::DroppedFile {
780 window: *window,
781 path_buf: path_buf.clone(),
782 },
783 });
784 }
785 FileDragAndDrop::HoveredFile { window, path_buf } => {
786 egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
787 context,
788 event: FileDragAndDrop::HoveredFile {
789 window: *window,
790 path_buf: path_buf.clone(),
791 },
792 });
793 }
794 FileDragAndDrop::HoveredFileCanceled { window } => {
795 egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
796 context,
797 event: FileDragAndDrop::HoveredFileCanceled { window: *window },
798 });
799 }
800 }
801 }
802}
803
804pub fn write_window_touch_events_system(
806 mut commands: Commands,
807 egui_global_settings: Res<EguiGlobalSettings>,
808 modifier_keys_state: Res<ModifierKeysState>,
809 mut touch_input_reader: EguiContextEventReader<TouchInput>,
810 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
811 mut egui_contexts: Query<
812 (
813 &EguiContextSettings,
814 &mut EguiContextPointerPosition,
815 &mut EguiContextPointerTouchId,
816 &EguiOutput,
817 ),
818 With<EguiContext>,
819 >,
820) {
821 let modifiers = modifier_keys_state.to_egui_modifiers();
822 let hovered_non_window_egui_context = touch_input_reader
823 .hovered_non_window_egui_context
824 .as_deref()
825 .cloned();
826
827 for (event, context) in touch_input_reader.read(|event| event.window) {
828 let Some((
829 context_settings,
830 mut context_pointer_position,
831 mut context_pointer_touch_id,
832 output,
833 )) = egui_contexts.get_some_mut(context)
834 else {
835 continue;
836 };
837
838 if egui_global_settings.enable_focused_non_window_context_updates {
839 if let bevy_input::touch::TouchPhase::Started = event.phase {
840 if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
841 if let bevy_input::touch::TouchPhase::Started = event.phase {
842 commands.insert_resource(FocusedNonWindowEguiContext(
843 hovered_non_window_egui_context.0,
844 ));
845 }
846
847 continue;
848 }
849
850 commands.remove_resource::<FocusedNonWindowEguiContext>();
851 }
852 }
853
854 if !context_settings
855 .input_system_settings
856 .run_write_window_touch_events_system
857 {
858 continue;
859 }
860
861 let scale_factor = context_settings.scale_factor;
862 let touch_position = vec2_into_egui_pos2(event.position / scale_factor);
863 context_pointer_position.position = touch_position;
864 write_touch_event(
865 &mut egui_input_event_writer,
866 event,
867 context,
868 output,
869 touch_position,
870 modifiers,
871 &mut context_pointer_touch_id,
872 );
873 }
874}
875
876pub fn write_non_window_touch_events_system(
878 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
879 mut touch_input_reader: EventReader<TouchInput>,
880 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
881 modifier_keys_state: Res<ModifierKeysState>,
882 mut egui_contexts: Query<
883 (
884 &EguiContextSettings,
885 &EguiContextPointerPosition,
886 &mut EguiContextPointerTouchId,
887 &EguiOutput,
888 ),
889 With<EguiContext>,
890 >,
891) {
892 let modifiers = modifier_keys_state.to_egui_modifiers();
893 for event in touch_input_reader.read() {
894 let Some(&FocusedNonWindowEguiContext(focused_non_window_egui_context)) =
895 focused_non_window_egui_context.as_deref()
896 else {
897 continue;
898 };
899
900 let Some((
901 context_settings,
902 context_pointer_position,
903 mut context_pointer_touch_id,
904 output,
905 )) = egui_contexts.get_some_mut(focused_non_window_egui_context)
906 else {
907 continue;
908 };
909
910 if !context_settings
911 .input_system_settings
912 .run_write_non_window_touch_events_system
913 {
914 continue;
915 }
916
917 write_touch_event(
918 &mut egui_input_event_writer,
919 event,
920 focused_non_window_egui_context,
921 output,
922 context_pointer_position.position,
923 modifiers,
924 &mut context_pointer_touch_id,
925 );
926 }
927}
928
929fn write_touch_event(
930 egui_input_event_writer: &mut EventWriter<EguiInputEvent>,
931 event: &TouchInput,
932 context: Entity,
933 _output: &EguiOutput,
934 pointer_position: egui::Pos2,
935 modifiers: Modifiers,
936 context_pointer_touch_id: &mut EguiContextPointerTouchId,
937) {
938 let touch_id = egui::TouchId::from(event.id);
939
940 egui_input_event_writer.write(EguiInputEvent {
942 context,
943 event: egui::Event::Touch {
944 device_id: egui::TouchDeviceId(event.window.to_bits()),
945 id: touch_id,
946 phase: match event.phase {
947 bevy_input::touch::TouchPhase::Started => egui::TouchPhase::Start,
948 bevy_input::touch::TouchPhase::Moved => egui::TouchPhase::Move,
949 bevy_input::touch::TouchPhase::Ended => egui::TouchPhase::End,
950 bevy_input::touch::TouchPhase::Canceled => egui::TouchPhase::Cancel,
951 },
952 pos: pointer_position,
953 force: match event.force {
954 Some(bevy_input::touch::ForceTouch::Normalized(force)) => Some(force as f32),
955 Some(bevy_input::touch::ForceTouch::Calibrated {
956 force,
957 max_possible_force,
958 ..
959 }) => Some((force / max_possible_force) as f32),
960 None => None,
961 },
962 },
963 });
964
965 if context_pointer_touch_id.pointer_touch_id.is_none()
968 || context_pointer_touch_id.pointer_touch_id.unwrap() == event.id
969 {
970 match event.phase {
972 bevy_input::touch::TouchPhase::Started => {
973 context_pointer_touch_id.pointer_touch_id = Some(event.id);
974 egui_input_event_writer.write(EguiInputEvent {
976 context,
977 event: egui::Event::PointerMoved(pointer_position),
978 });
979 egui_input_event_writer.write(EguiInputEvent {
981 context,
982 event: egui::Event::PointerButton {
983 pos: pointer_position,
984 button: egui::PointerButton::Primary,
985 pressed: true,
986 modifiers,
987 },
988 });
989 }
990 bevy_input::touch::TouchPhase::Moved => {
991 egui_input_event_writer.write(EguiInputEvent {
992 context,
993 event: egui::Event::PointerMoved(pointer_position),
994 });
995 }
996 bevy_input::touch::TouchPhase::Ended => {
997 context_pointer_touch_id.pointer_touch_id = None;
998 egui_input_event_writer.write(EguiInputEvent {
999 context,
1000 event: egui::Event::PointerButton {
1001 pos: pointer_position,
1002 button: egui::PointerButton::Primary,
1003 pressed: false,
1004 modifiers,
1005 },
1006 });
1007 egui_input_event_writer.write(EguiInputEvent {
1008 context,
1009 event: egui::Event::PointerGone,
1010 });
1011
1012 #[cfg(target_arch = "wasm32")]
1013 if !is_mobile_safari() {
1014 update_text_agent(
1015 _output.platform_output.ime.is_some()
1016 || _output.platform_output.mutable_text_under_cursor,
1017 );
1018 }
1019 }
1020 bevy_input::touch::TouchPhase::Canceled => {
1021 context_pointer_touch_id.pointer_touch_id = None;
1022 egui_input_event_writer.write(EguiInputEvent {
1023 context,
1024 event: egui::Event::PointerGone,
1025 });
1026 }
1027 }
1028 }
1029}
1030
1031#[allow(clippy::too_many_arguments)]
1033pub fn write_egui_input_system(
1034 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
1035 window_to_egui_context_map: Res<WindowToEguiContextMap>,
1036 modifier_keys_state: Res<ModifierKeysState>,
1037 mut egui_input_event_reader: EventReader<EguiInputEvent>,
1038 mut egui_file_dnd_event_reader: EventReader<EguiFileDragAndDropEvent>,
1039 mut egui_contexts: Query<(Entity, &mut EguiInput)>,
1040 windows: Query<&Window>,
1041 time: Res<Time<Real>>,
1042) {
1043 for EguiInputEvent { context, event } in egui_input_event_reader.read() {
1044 #[cfg(feature = "log_input_events")]
1045 log::warn!("{context:?}: {event:?}");
1046
1047 let (_, mut egui_input) = match egui_contexts.get_mut(*context) {
1048 Ok(egui_input) => egui_input,
1049 Err(err) => {
1050 log::error!(
1051 "Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
1052 );
1053 continue;
1054 }
1055 };
1056
1057 egui_input.events.push(event.clone());
1058 }
1059
1060 for EguiFileDragAndDropEvent { context, event } in egui_file_dnd_event_reader.read() {
1061 #[cfg(feature = "log_file_dnd_events")]
1062 log::warn!("{context:?}: {event:?}");
1063
1064 let (_, mut egui_input) = match egui_contexts.get_mut(*context) {
1065 Ok(egui_input) => egui_input,
1066 Err(err) => {
1067 log::error!(
1068 "Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
1069 );
1070 continue;
1071 }
1072 };
1073
1074 match event {
1075 FileDragAndDrop::DroppedFile {
1076 window: _,
1077 path_buf,
1078 } => {
1079 egui_input.hovered_files.clear();
1080 egui_input.dropped_files.push(egui::DroppedFile {
1081 path: Some(path_buf.clone()),
1082 ..Default::default()
1083 });
1084 }
1085 FileDragAndDrop::HoveredFile {
1086 window: _,
1087 path_buf,
1088 } => {
1089 egui_input.hovered_files.push(egui::HoveredFile {
1090 path: Some(path_buf.clone()),
1091 ..Default::default()
1092 });
1093 }
1094 FileDragAndDrop::HoveredFileCanceled { window: _ } => {
1095 egui_input.hovered_files.clear();
1096 }
1097 }
1098 }
1099
1100 for (entity, mut egui_input) in egui_contexts.iter_mut() {
1101 egui_input.focused = focused_non_window_egui_context.as_deref().map_or_else(
1102 || {
1103 window_to_egui_context_map
1104 .context_to_window
1105 .get(&entity)
1106 .and_then(|window_entity| windows.get_some(*window_entity))
1107 .is_some_and(|window| window.focused)
1108 },
1109 |context| context.0 == entity,
1110 );
1111 egui_input.modifiers = modifier_keys_state.to_egui_modifiers();
1112 egui_input.time = Some(time.elapsed_secs_f64());
1113 }
1114}
1115
1116pub fn absorb_bevy_input_system(
1134 egui_wants_input: Res<EguiWantsInput>,
1135 mut mouse_input: ResMut<ButtonInput<MouseButton>>,
1136 mut keyboard_input: ResMut<ButtonInput<KeyCode>>,
1137 mut keyboard_input_events: ResMut<Events<KeyboardInput>>,
1138 mut mouse_wheel_events: ResMut<Events<MouseWheel>>,
1139 mut mouse_button_input_events: ResMut<Events<MouseButtonInput>>,
1140) {
1141 let modifiers = [
1142 KeyCode::SuperLeft,
1143 KeyCode::SuperRight,
1144 KeyCode::ControlLeft,
1145 KeyCode::ControlRight,
1146 KeyCode::AltLeft,
1147 KeyCode::AltRight,
1148 KeyCode::ShiftLeft,
1149 KeyCode::ShiftRight,
1150 ];
1151
1152 let pressed = modifiers.map(|key| keyboard_input.pressed(key).then_some(key));
1153
1154 if egui_wants_input.wants_any_keyboard_input() {
1157 keyboard_input.reset_all();
1158 keyboard_input_events.clear();
1159 }
1160 if egui_wants_input.wants_any_pointer_input() {
1161 mouse_input.reset_all();
1162 mouse_wheel_events.clear();
1163 mouse_button_input_events.clear();
1164 }
1165
1166 for key in pressed.into_iter().flatten() {
1167 keyboard_input.press(key);
1168 }
1169}
1170
1171#[derive(Resource, Clone, Debug, Default)]
1173pub struct EguiWantsInput {
1174 is_pointer_over_area: bool,
1175 wants_pointer_input: bool,
1176 is_using_pointer: bool,
1177 wants_keyboard_input: bool,
1178 is_context_menu_open: bool,
1179}
1180
1181impl EguiWantsInput {
1182 pub fn is_pointer_over_area(&self) -> bool {
1184 self.is_pointer_over_area
1185 }
1186
1187 pub fn wants_pointer_input(&self) -> bool {
1194 self.wants_pointer_input
1195 }
1196
1197 pub fn is_using_pointer(&self) -> bool {
1201 self.is_using_pointer
1202 }
1203
1204 pub fn wants_keyboard_input(&self) -> bool {
1206 self.wants_keyboard_input
1207 }
1208
1209 pub fn is_context_menu_open(&self) -> bool {
1211 self.is_context_menu_open
1212 }
1213
1214 pub fn wants_any_pointer_input(&self) -> bool {
1217 self.is_pointer_over_area
1218 || self.wants_pointer_input
1219 || self.is_using_pointer
1220 || self.is_context_menu_open
1221 }
1222
1223 pub fn wants_any_keyboard_input(&self) -> bool {
1226 self.wants_keyboard_input || self.is_context_menu_open
1227 }
1228
1229 pub fn wants_any_input(&self) -> bool {
1232 self.wants_any_pointer_input() || self.wants_any_keyboard_input()
1233 }
1234
1235 fn reset(&mut self) {
1236 self.is_pointer_over_area = false;
1237 self.wants_pointer_input = false;
1238 self.is_using_pointer = false;
1239 self.wants_keyboard_input = false;
1240 self.is_context_menu_open = false;
1241 }
1242}
1243
1244pub fn write_egui_wants_input_system(
1246 mut egui_context_query: Query<&mut EguiContext>,
1247 mut egui_wants_input: ResMut<EguiWantsInput>,
1248) {
1249 egui_wants_input.reset();
1250
1251 for mut ctx in egui_context_query.iter_mut() {
1252 let egui_ctx = ctx.get_mut();
1253 egui_wants_input.is_pointer_over_area =
1254 egui_wants_input.is_pointer_over_area || egui_ctx.is_pointer_over_area();
1255 egui_wants_input.wants_pointer_input =
1256 egui_wants_input.wants_pointer_input || egui_ctx.wants_pointer_input();
1257 egui_wants_input.is_using_pointer =
1258 egui_wants_input.is_using_pointer || egui_ctx.is_using_pointer();
1259 egui_wants_input.wants_keyboard_input =
1260 egui_wants_input.wants_keyboard_input || egui_ctx.wants_keyboard_input();
1261 egui_wants_input.is_context_menu_open =
1262 egui_wants_input.is_context_menu_open || egui_ctx.is_context_menu_open();
1263 }
1264}
1265
1266pub fn egui_wants_any_pointer_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1269 egui_wants_input_resource.wants_any_pointer_input()
1270}
1271
1272pub fn egui_wants_any_keyboard_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1275 egui_wants_input_resource.wants_any_keyboard_input()
1276}
1277
1278pub fn egui_wants_any_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1281 egui_wants_input_resource.wants_any_input()
1282}