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