1#[cfg(target_arch = "wasm32")]
2use crate::text_agent::{is_mobile_safari, update_text_agent};
3use crate::{
4 EguiContext, EguiContextSettings, EguiGlobalSettings, EguiInput, EguiOutput,
5 helpers::{QueryHelper, vec2_into_egui_pos2},
6};
7use bevy_ecs::{
8 message::MessageIterator,
9 prelude::*,
10 system::{NonSendMarker, SystemParam},
11};
12use bevy_input::{
13 ButtonInput, ButtonState,
14 keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput},
15 mouse::{MouseButton, MouseButtonInput, MouseScrollUnit, MouseWheel},
16 touch::TouchInput,
17};
18use bevy_log::{self as log};
19use bevy_time::{Real, Time};
20use bevy_window::{CursorMoved, FileDragAndDrop, Ime, Window};
21use egui::Modifiers;
22
23#[derive(Component, Default)]
25pub struct EguiContextPointerPosition {
26 pub position: egui::Pos2,
28}
29
30#[derive(Component, Default)]
32pub struct EguiContextPointerTouchId {
33 pub pointer_touch_id: Option<u64>,
35}
36
37#[derive(Component, Default)]
39pub struct EguiContextImeState {
40 pub has_sent_ime_enabled: bool,
42 pub is_ime_allowed: bool,
44 pub ime_rect: Option<egui::Rect>,
46}
47
48#[derive(Message)]
49pub struct EguiInputEvent {
51 pub context: Entity,
53 pub event: egui::Event,
55}
56
57#[derive(Message)]
58pub struct EguiFileDragAndDropMessage {
60 pub context: Entity,
62 pub message: FileDragAndDrop,
64}
65
66#[derive(Resource, Clone)]
67pub struct HoveredNonWindowEguiContext(pub Entity);
73
74#[derive(Resource, Clone)]
88pub struct FocusedNonWindowEguiContext(pub Entity);
89
90#[derive(Resource, Clone, Copy, Debug)]
92pub struct ModifierKeysState {
93 pub shift: bool,
95 pub ctrl: bool,
97 pub alt: bool,
99 pub win: bool,
101 is_macos: bool,
102}
103
104impl Default for ModifierKeysState {
105 fn default() -> Self {
106 let mut state = Self {
107 shift: false,
108 ctrl: false,
109 alt: false,
110 win: false,
111 is_macos: false,
112 };
113
114 #[cfg(not(target_arch = "wasm32"))]
115 {
116 state.is_macos = cfg!(target_os = "macos");
117 }
118
119 #[cfg(target_arch = "wasm32")]
120 if let Some(window) = web_sys::window() {
121 let nav = window.navigator();
122 if let Ok(user_agent) = nav.user_agent() {
123 if user_agent.to_ascii_lowercase().contains("mac") {
124 state.is_macos = true;
125 }
126 }
127 }
128
129 state
130 }
131}
132
133impl ModifierKeysState {
134 pub fn to_egui_modifiers(&self) -> egui::Modifiers {
136 egui::Modifiers {
137 alt: self.alt,
138 ctrl: self.ctrl,
139 shift: self.shift,
140 mac_cmd: if self.is_macos { self.win } else { false },
141 command: if self.is_macos { self.win } else { self.ctrl },
142 }
143 }
144
145 pub fn text_input_is_allowed(&self) -> bool {
147 !self.win && !self.ctrl || !self.is_macos && self.ctrl && self.alt
149 }
150
151 fn reset(&mut self) {
152 self.shift = false;
153 self.ctrl = false;
154 self.alt = false;
155 self.win = false;
156 }
157}
158
159#[derive(Resource, Default)]
160pub struct WindowToEguiContextMap {
163 pub window_to_contexts:
165 bevy_platform::collections::HashMap<Entity, bevy_platform::collections::HashSet<Entity>>,
166 pub context_to_window: bevy_platform::collections::HashMap<Entity, Entity>,
168}
169
170impl WindowToEguiContextMap {
171 pub fn on_egui_context_added_system(
173 mut res: ResMut<Self>,
174 added_contexts: Query<(Entity, &bevy_camera::Camera, &mut EguiContext), Added<EguiContext>>,
175 primary_window: Query<Entity, With<bevy_window::PrimaryWindow>>,
176 event_loop_proxy: Res<bevy_winit::EventLoopProxyWrapper<bevy_winit::WakeUp>>,
177 ) {
178 for (egui_context_entity, camera, mut egui_context) in added_contexts {
179 if let bevy_camera::RenderTarget::Window(window_ref) = camera.target
180 && let Some(window_ref) = window_ref.normalize(primary_window.single().ok())
181 {
182 res.window_to_contexts
183 .entry(window_ref.entity())
184 .or_default()
185 .insert(egui_context_entity);
186 res.context_to_window
187 .insert(egui_context_entity, window_ref.entity());
188
189 let message_loop_proxy = (*event_loop_proxy).clone();
191 egui_context
192 .get_mut()
193 .set_request_repaint_callback(move |repaint_info| {
194 if repaint_info.delay.is_zero() {
197 log::trace!("Sending the WakeUp message");
198 let _ = message_loop_proxy.send_event(bevy_winit::WakeUp);
199 }
200 });
201 }
202 }
203 }
204
205 pub fn on_egui_context_removed_system(
207 mut res: ResMut<Self>,
208 mut removed_contexts: RemovedComponents<EguiContext>,
209 ) {
210 for egui_context_entity in removed_contexts.read() {
211 let Some(window_entity) = res.context_to_window.remove(&egui_context_entity) else {
212 continue;
213 };
214
215 let Some(window_contexts) = res.window_to_contexts.get_mut(&window_entity) else {
216 log::warn!(
217 "A destroyed Egui context's window isn't registered: {egui_context_entity:?}"
218 );
219 continue;
220 };
221
222 window_contexts.remove(&egui_context_entity);
223 }
224 }
225}
226
227pub struct EguiContextsMessageIterator<'a, M: Message, F> {
229 message_iter: MessageIterator<'a, M>,
230 map_message_to_window_id_f: F,
231 current_message: Option<&'a M>,
232 current_message_contexts: Vec<Entity>,
233 non_window_context: Option<Entity>,
234 map: &'a WindowToEguiContextMap,
235}
236
237impl<'a, M: Message, F: FnMut(&'a M) -> Entity> Iterator for EguiContextsMessageIterator<'a, M, F> {
238 type Item = (&'a M, Entity);
239
240 fn next(&mut self) -> Option<Self::Item> {
241 if self.current_message_contexts.is_empty() {
242 self.current_message = None;
243 }
244
245 if self.current_message.is_none() {
246 self.current_message = self.message_iter.next();
247
248 if self.non_window_context.is_some() {
249 return self.current_message.zip(self.non_window_context);
250 }
251
252 if let Some(current) = self.current_message {
253 if let Some(contexts) = self
254 .map
255 .window_to_contexts
256 .get(&(self.map_message_to_window_id_f)(current))
257 {
258 self.current_message_contexts = contexts.iter().cloned().collect();
259 }
260 }
261 }
262
263 self.current_message
264 .zip(self.current_message_contexts.pop())
265 }
266}
267
268#[derive(SystemParam)]
269pub struct EguiContextMessageReader<'w, 's, M: Message> {
271 message_reader: MessageReader<'w, 's, M>,
272 map: Res<'w, WindowToEguiContextMap>,
273 hovered_non_window_egui_context: Option<Res<'w, HoveredNonWindowEguiContext>>,
274 focused_non_window_egui_context: Option<Res<'w, FocusedNonWindowEguiContext>>,
275}
276
277impl<'w, 's, M: Message> EguiContextMessageReader<'w, 's, M> {
278 pub fn read<'a, F>(
281 &'a mut self,
282 map_message_to_window_id_f: F,
283 ) -> EguiContextsMessageIterator<'a, M, F>
284 where
285 F: FnMut(&'a M) -> Entity,
286 M: Message,
287 {
288 EguiContextsMessageIterator {
289 message_iter: self.message_reader.read(),
290 map_message_to_window_id_f,
291 current_message: None,
292 current_message_contexts: Vec::new(),
293 non_window_context: None,
294 map: &self.map,
295 }
296 }
297
298 pub fn read_with_non_window_hovered<'a, F>(
300 &'a mut self,
301 map_message_to_window_id_f: F,
302 ) -> EguiContextsMessageIterator<'a, M, F>
303 where
304 F: FnMut(&'a M) -> Entity,
305 M: Message,
306 {
307 EguiContextsMessageIterator {
308 message_iter: self.message_reader.read(),
309 map_message_to_window_id_f,
310 current_message: None,
311 current_message_contexts: Vec::new(),
312 non_window_context: self
313 .hovered_non_window_egui_context
314 .as_deref()
315 .map(|context| context.0),
316 map: &self.map,
317 }
318 }
319
320 pub fn read_with_non_window_focused<'a, F>(
322 &'a mut self,
323 map_message_to_window_id_f: F,
324 ) -> EguiContextsMessageIterator<'a, M, F>
325 where
326 F: FnMut(&'a M) -> Entity,
327 M: Message,
328 {
329 EguiContextsMessageIterator {
330 message_iter: self.message_reader.read(),
331 map_message_to_window_id_f,
332 current_message: None,
333 current_message_contexts: Vec::new(),
334 non_window_context: self
335 .focused_non_window_egui_context
336 .as_deref()
337 .map(|context| context.0),
338 map: &self.map,
339 }
340 }
341}
342
343pub fn write_modifiers_keys_state_system(
345 mut keyboard_input_reader: MessageReader<KeyboardInput>,
346 mut focus_reader: MessageReader<KeyboardFocusLost>,
347 mut modifier_keys_state: ResMut<ModifierKeysState>,
348) {
349 if !focus_reader.is_empty() {
351 focus_reader.clear();
352 modifier_keys_state.reset();
353 }
354
355 for message in keyboard_input_reader.read() {
356 let KeyboardInput {
357 logical_key, state, ..
358 } = message;
359 match logical_key {
360 Key::Shift => {
361 modifier_keys_state.shift = state.is_pressed();
362 }
363 Key::Control => {
364 modifier_keys_state.ctrl = state.is_pressed();
365 }
366 Key::Alt => {
367 modifier_keys_state.alt = state.is_pressed();
368 }
369 Key::Super | Key::Meta => {
370 modifier_keys_state.win = state.is_pressed();
371 }
372 _ => {}
373 };
374 }
375}
376
377pub fn write_window_pointer_moved_messages_system(
379 mut cursor_moved_reader: EguiContextMessageReader<CursorMoved>,
380 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
381 mut egui_contexts: Query<
382 (&EguiContextSettings, &mut EguiContextPointerPosition),
383 With<EguiContext>,
384 >,
385) {
386 for (message, context) in cursor_moved_reader.read(|message| message.window) {
387 let Some((context_settings, mut context_pointer_position)) =
388 egui_contexts.get_some_mut(context)
389 else {
390 continue;
391 };
392
393 if !context_settings
394 .input_system_settings
395 .run_write_window_pointer_moved_messages_system
396 {
397 continue;
398 }
399
400 let scale_factor = context_settings.scale_factor;
401 let pointer_position = vec2_into_egui_pos2(message.position / scale_factor);
402 context_pointer_position.position = pointer_position;
403 egui_input_message_writer.write(EguiInputEvent {
404 context,
405 event: egui::Event::PointerMoved(pointer_position),
406 });
407 }
408}
409
410pub fn write_pointer_button_messages_system(
413 egui_global_settings: Res<EguiGlobalSettings>,
414 mut commands: Commands,
415 modifier_keys_state: Res<ModifierKeysState>,
416 mut mouse_button_input_reader: EguiContextMessageReader<MouseButtonInput>,
417 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
418 egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
419) {
420 let modifiers = modifier_keys_state.to_egui_modifiers();
421 let hovered_non_window_egui_context = mouse_button_input_reader
422 .hovered_non_window_egui_context
423 .as_deref()
424 .cloned();
425 for (message, context) in
426 mouse_button_input_reader.read_with_non_window_hovered(|message| message.window)
427 {
428 let Some((context_settings, context_pointer_position)) = egui_contexts.get_some(context)
429 else {
430 continue;
431 };
432
433 if !context_settings
434 .input_system_settings
435 .run_write_pointer_button_messages_system
436 {
437 continue;
438 }
439
440 let button = match message.button {
441 MouseButton::Left => Some(egui::PointerButton::Primary),
442 MouseButton::Right => Some(egui::PointerButton::Secondary),
443 MouseButton::Middle => Some(egui::PointerButton::Middle),
444 MouseButton::Back => Some(egui::PointerButton::Extra1),
445 MouseButton::Forward => Some(egui::PointerButton::Extra2),
446 _ => None,
447 };
448 let Some(button) = button else {
449 continue;
450 };
451 let pressed = match message.state {
452 ButtonState::Pressed => true,
453 ButtonState::Released => false,
454 };
455 egui_input_message_writer.write(EguiInputEvent {
456 context,
457 event: egui::Event::PointerButton {
458 pos: context_pointer_position.position,
459 button,
460 pressed,
461 modifiers,
462 },
463 });
464
465 if egui_global_settings.enable_focused_non_window_context_updates && pressed {
467 if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
468 commands.insert_resource(FocusedNonWindowEguiContext(
469 hovered_non_window_egui_context.0,
470 ));
471 } else {
472 commands.remove_resource::<FocusedNonWindowEguiContext>();
473 }
474 }
475 }
476}
477
478pub fn write_non_window_pointer_moved_messages_system(
480 hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
481 mut cursor_moved_reader: MessageReader<CursorMoved>,
482 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
483 egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
484) {
485 if cursor_moved_reader.is_empty() {
486 return;
487 }
488
489 cursor_moved_reader.clear();
490 let Some(HoveredNonWindowEguiContext(hovered_non_window_egui_context)) =
491 hovered_non_window_egui_context.as_deref()
492 else {
493 return;
494 };
495
496 let Some((context_settings, context_pointer_position)) =
497 egui_contexts.get_some(*hovered_non_window_egui_context)
498 else {
499 return;
500 };
501
502 if !context_settings
503 .input_system_settings
504 .run_write_non_window_pointer_moved_messages_system
505 {
506 return;
507 }
508
509 egui_input_message_writer.write(EguiInputEvent {
510 context: *hovered_non_window_egui_context,
511 event: egui::Event::PointerMoved(context_pointer_position.position),
512 });
513}
514
515pub fn write_mouse_wheel_messages_system(
517 modifier_keys_state: Res<ModifierKeysState>,
518 mut mouse_wheel_reader: EguiContextMessageReader<MouseWheel>,
519 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
520 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
521) {
522 let modifiers = modifier_keys_state.to_egui_modifiers();
523 for (message, context) in
524 mouse_wheel_reader.read_with_non_window_hovered(|message| message.window)
525 {
526 let delta = egui::vec2(message.x, message.y);
527 let unit = match message.unit {
528 MouseScrollUnit::Line => egui::MouseWheelUnit::Line,
529 MouseScrollUnit::Pixel => egui::MouseWheelUnit::Point,
530 };
531
532 let Some(context_settings) = egui_contexts.get_some(context) else {
533 continue;
534 };
535
536 if !context_settings
537 .input_system_settings
538 .run_write_mouse_wheel_messages_system
539 {
540 continue;
541 }
542
543 egui_input_message_writer.write(EguiInputEvent {
544 context,
545 event: egui::Event::MouseWheel {
546 unit,
547 delta,
548 modifiers,
549 },
550 });
551 }
552}
553
554pub fn write_keyboard_input_messages_system(
556 modifier_keys_state: Res<ModifierKeysState>,
557 #[cfg(all(
558 feature = "manage_clipboard",
559 not(target_os = "android"),
560 not(target_arch = "wasm32")
561 ))]
562 mut egui_clipboard: ResMut<crate::EguiClipboard>,
563 mut keyboard_input_reader: EguiContextMessageReader<KeyboardInput>,
564 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
565 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
566) {
567 let modifiers = modifier_keys_state.to_egui_modifiers();
568 for (message, context) in
569 keyboard_input_reader.read_with_non_window_focused(|message| message.window)
570 {
571 let Some(context_settings) = egui_contexts.get_some(context) else {
572 continue;
573 };
574
575 if !context_settings
576 .input_system_settings
577 .run_write_keyboard_input_messages_system
578 {
579 continue;
580 }
581
582 if modifier_keys_state.text_input_is_allowed() && message.state.is_pressed() {
583 match &message.logical_key {
584 Key::Character(char) if char.matches(char::is_control).count() == 0 => {
585 egui_input_message_writer.write(EguiInputEvent {
586 context,
587 event: egui::Event::Text(char.to_string()),
588 });
589 }
590 Key::Space => {
591 egui_input_message_writer.write(EguiInputEvent {
592 context,
593 event: egui::Event::Text(" ".to_string()),
594 });
595 }
596 _ => (),
597 }
598 }
599
600 let key = crate::helpers::bevy_to_egui_key(&message.logical_key);
601 let physical_key = crate::helpers::bevy_to_egui_physical_key(&message.key_code);
602
603 let (Some(key), physical_key) = (key.or(physical_key), physical_key) else {
606 continue;
607 };
608
609 let egui_message = egui::Event::Key {
610 key,
611 pressed: message.state.is_pressed(),
612 repeat: false,
613 modifiers,
614 physical_key,
615 };
616 egui_input_message_writer.write(EguiInputEvent {
617 context,
618 event: egui_message,
619 });
620
621 #[cfg(all(
624 feature = "manage_clipboard",
625 not(target_os = "android"),
626 not(target_arch = "wasm32")
627 ))]
628 if modifiers.command && message.state.is_pressed() {
629 match key {
630 egui::Key::C => {
631 egui_input_message_writer.write(EguiInputEvent {
632 context,
633 event: egui::Event::Copy,
634 });
635 }
636 egui::Key::X => {
637 egui_input_message_writer.write(EguiInputEvent {
638 context,
639 event: egui::Event::Cut,
640 });
641 }
642 egui::Key::V => {
643 if let Some(contents) = egui_clipboard.get_text() {
644 egui_input_message_writer.write(EguiInputEvent {
645 context,
646 event: egui::Event::Text(contents),
647 });
648 }
649 }
650 _ => {}
651 }
652 }
653 }
654}
655
656pub fn write_ime_messages_system(
658 mut ime_reader: EguiContextMessageReader<Ime>,
659 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
660 mut egui_contexts: Query<
661 (
662 Entity,
663 &EguiContextSettings,
664 &mut EguiContextImeState,
665 &EguiOutput,
666 ),
667 With<EguiContext>,
668 >,
669) {
670 for (message, context) in ime_reader.read_with_non_window_focused(|message| match &message {
671 Ime::Preedit { window, .. }
672 | Ime::Commit { window, .. }
673 | Ime::Disabled { window }
674 | Ime::Enabled { window } => *window,
675 }) {
676 let Some((_entity, context_settings, mut ime_state, _egui_output)) =
677 egui_contexts.get_some_mut(context)
678 else {
679 continue;
680 };
681
682 if !context_settings
683 .input_system_settings
684 .run_write_ime_messages_system
685 {
686 continue;
687 }
688
689 let ime_message_enable =
690 |ime_state: &mut EguiContextImeState,
691 egui_input_message_writer: &mut MessageWriter<EguiInputEvent>| {
692 if !ime_state.has_sent_ime_enabled {
693 egui_input_message_writer.write(EguiInputEvent {
694 context,
695 event: egui::Event::Ime(egui::ImeEvent::Enabled),
696 });
697 ime_state.has_sent_ime_enabled = true;
698 }
699 };
700
701 let ime_message_disable =
702 |ime_state: &mut EguiContextImeState,
703 egui_input_message_writer: &mut MessageWriter<EguiInputEvent>| {
704 if !ime_state.has_sent_ime_enabled {
705 egui_input_message_writer.write(EguiInputEvent {
706 context,
707 event: egui::Event::Ime(egui::ImeEvent::Disabled),
708 });
709 ime_state.has_sent_ime_enabled = false;
710 }
711 };
712
713 match message {
715 Ime::Enabled { window: _ } => {
716 ime_message_enable(&mut ime_state, &mut egui_input_message_writer);
717 }
718 Ime::Preedit {
719 value,
720 window: _,
721 cursor: Some(_),
722 } => {
723 ime_message_enable(&mut ime_state, &mut egui_input_message_writer);
724 egui_input_message_writer.write(EguiInputEvent {
725 context,
726 event: egui::Event::Ime(egui::ImeEvent::Preedit(value.clone())),
727 });
728 }
729 Ime::Commit { value, window: _ } => {
730 egui_input_message_writer.write(EguiInputEvent {
731 context,
732 event: egui::Event::Ime(egui::ImeEvent::Commit(value.clone())),
733 });
734 ime_message_disable(&mut ime_state, &mut egui_input_message_writer);
735 }
736 Ime::Disabled { window: _ }
737 | Ime::Preedit {
738 cursor: None,
739 window: _,
740 value: _,
741 } => {
742 ime_message_disable(&mut ime_state, &mut egui_input_message_writer);
743 }
744 }
745 }
746}
747
748pub fn process_ime_system(
751 mut egui_context_query: Query<(
752 Entity,
753 &EguiOutput,
754 &EguiContextSettings,
755 &mut EguiContext,
756 &mut EguiContextImeState,
757 )>,
758 window_to_egui_context_map: Res<WindowToEguiContextMap>,
759 _non_send_marker: NonSendMarker,
760) {
761 for (entity, egui_output, egui_settings, mut egui_context, mut egui_ime_state) in
762 &mut egui_context_query
763 {
764 let Some(window_entity) = window_to_egui_context_map.context_to_window.get(&entity) else {
765 continue;
766 };
767
768 bevy_winit::WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
769 let Some(winit_window) = winit_windows.get_window(*window_entity) else {
770 log::warn!(
771 "Cannot access an underlying winit window for a window entity {}",
772 window_entity
773 );
774
775 return;
776 };
777
778 let ime_allowed = egui_output.platform_output.ime.is_some();
779 if ime_allowed != egui_ime_state.is_ime_allowed {
780 winit_window.set_ime_allowed(ime_allowed);
781 egui_ime_state.is_ime_allowed = ime_allowed;
782 }
783
784 if let Some(ime) = egui_output.platform_output.ime {
785 let ime_rect_px = ime.rect * egui_settings.scale_factor;
786 if egui_ime_state.ime_rect != Some(ime_rect_px)
787 || egui_context.get_mut().input(|i| !i.events.is_empty())
788 {
789 egui_ime_state.ime_rect = Some(ime_rect_px);
790 winit_window.set_ime_cursor_area(
791 winit::dpi::LogicalPosition {
792 x: ime_rect_px.min.x,
793 y: ime_rect_px.min.y,
794 },
795 winit::dpi::LogicalSize {
796 width: ime_rect_px.width(),
797 height: ime_rect_px.height(),
798 },
799 );
800 }
801 } else {
802 egui_ime_state.ime_rect = None;
803 }
804 });
805 }
806}
807
808pub fn write_file_dnd_messages_system(
810 mut dnd_reader: EguiContextMessageReader<FileDragAndDrop>,
811 mut egui_file_dnd_message_writer: MessageWriter<EguiFileDragAndDropMessage>,
812 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
813) {
814 for (message, context) in dnd_reader.read_with_non_window_hovered(|message| match &message {
815 FileDragAndDrop::DroppedFile { window, .. }
816 | FileDragAndDrop::HoveredFile { window, .. }
817 | FileDragAndDrop::HoveredFileCanceled { window } => *window,
818 }) {
819 let Some(context_settings) = egui_contexts.get_some(context) else {
820 continue;
821 };
822
823 if !context_settings
824 .input_system_settings
825 .run_write_file_dnd_messages_system
826 {
827 continue;
828 }
829
830 match message {
831 FileDragAndDrop::DroppedFile { window, path_buf } => {
832 egui_file_dnd_message_writer.write(EguiFileDragAndDropMessage {
833 context,
834 message: FileDragAndDrop::DroppedFile {
835 window: *window,
836 path_buf: path_buf.clone(),
837 },
838 });
839 }
840 FileDragAndDrop::HoveredFile { window, path_buf } => {
841 egui_file_dnd_message_writer.write(EguiFileDragAndDropMessage {
842 context,
843 message: FileDragAndDrop::HoveredFile {
844 window: *window,
845 path_buf: path_buf.clone(),
846 },
847 });
848 }
849 FileDragAndDrop::HoveredFileCanceled { window } => {
850 egui_file_dnd_message_writer.write(EguiFileDragAndDropMessage {
851 context,
852 message: FileDragAndDrop::HoveredFileCanceled { window: *window },
853 });
854 }
855 }
856 }
857}
858
859pub fn write_window_touch_messages_system(
861 mut commands: Commands,
862 egui_global_settings: Res<EguiGlobalSettings>,
863 modifier_keys_state: Res<ModifierKeysState>,
864 mut touch_input_reader: EguiContextMessageReader<TouchInput>,
865 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
866 mut egui_contexts: Query<
867 (
868 &EguiContextSettings,
869 &mut EguiContextPointerPosition,
870 &mut EguiContextPointerTouchId,
871 &EguiOutput,
872 ),
873 With<EguiContext>,
874 >,
875) {
876 let modifiers = modifier_keys_state.to_egui_modifiers();
877 let hovered_non_window_egui_context = touch_input_reader
878 .hovered_non_window_egui_context
879 .as_deref()
880 .cloned();
881
882 for (message, context) in touch_input_reader.read(|message| message.window) {
883 let Some((
884 context_settings,
885 mut context_pointer_position,
886 mut context_pointer_touch_id,
887 output,
888 )) = egui_contexts.get_some_mut(context)
889 else {
890 continue;
891 };
892
893 if egui_global_settings.enable_focused_non_window_context_updates {
894 if let bevy_input::touch::TouchPhase::Started = message.phase {
895 if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
896 if let bevy_input::touch::TouchPhase::Started = message.phase {
897 commands.insert_resource(FocusedNonWindowEguiContext(
898 hovered_non_window_egui_context.0,
899 ));
900 }
901
902 continue;
903 }
904
905 commands.remove_resource::<FocusedNonWindowEguiContext>();
906 }
907 }
908
909 if !context_settings
910 .input_system_settings
911 .run_write_window_touch_messages_system
912 {
913 continue;
914 }
915
916 let scale_factor = context_settings.scale_factor;
917 let touch_position = vec2_into_egui_pos2(message.position / scale_factor);
918 context_pointer_position.position = touch_position;
919 write_touch_message(
920 &mut egui_input_message_writer,
921 message,
922 context,
923 output,
924 touch_position,
925 modifiers,
926 &mut context_pointer_touch_id,
927 );
928 }
929}
930
931pub fn write_non_window_touch_messages_system(
933 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
934 mut touch_input_reader: MessageReader<TouchInput>,
935 mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
936 modifier_keys_state: Res<ModifierKeysState>,
937 mut egui_contexts: Query<
938 (
939 &EguiContextSettings,
940 &EguiContextPointerPosition,
941 &mut EguiContextPointerTouchId,
942 &EguiOutput,
943 ),
944 With<EguiContext>,
945 >,
946) {
947 let modifiers = modifier_keys_state.to_egui_modifiers();
948 for message in touch_input_reader.read() {
949 let Some(&FocusedNonWindowEguiContext(focused_non_window_egui_context)) =
950 focused_non_window_egui_context.as_deref()
951 else {
952 continue;
953 };
954
955 let Some((
956 context_settings,
957 context_pointer_position,
958 mut context_pointer_touch_id,
959 output,
960 )) = egui_contexts.get_some_mut(focused_non_window_egui_context)
961 else {
962 continue;
963 };
964
965 if !context_settings
966 .input_system_settings
967 .run_write_non_window_touch_messages_system
968 {
969 continue;
970 }
971
972 write_touch_message(
973 &mut egui_input_message_writer,
974 message,
975 focused_non_window_egui_context,
976 output,
977 context_pointer_position.position,
978 modifiers,
979 &mut context_pointer_touch_id,
980 );
981 }
982}
983
984fn write_touch_message(
985 egui_input_message_writer: &mut MessageWriter<EguiInputEvent>,
986 message: &TouchInput,
987 context: Entity,
988 _output: &EguiOutput,
989 pointer_position: egui::Pos2,
990 modifiers: Modifiers,
991 context_pointer_touch_id: &mut EguiContextPointerTouchId,
992) {
993 let touch_id = egui::TouchId::from(message.id);
994
995 egui_input_message_writer.write(EguiInputEvent {
997 context,
998 event: egui::Event::Touch {
999 device_id: egui::TouchDeviceId(message.window.to_bits()),
1000 id: touch_id,
1001 phase: match message.phase {
1002 bevy_input::touch::TouchPhase::Started => egui::TouchPhase::Start,
1003 bevy_input::touch::TouchPhase::Moved => egui::TouchPhase::Move,
1004 bevy_input::touch::TouchPhase::Ended => egui::TouchPhase::End,
1005 bevy_input::touch::TouchPhase::Canceled => egui::TouchPhase::Cancel,
1006 },
1007 pos: pointer_position,
1008 force: match message.force {
1009 Some(bevy_input::touch::ForceTouch::Normalized(force)) => Some(force as f32),
1010 Some(bevy_input::touch::ForceTouch::Calibrated {
1011 force,
1012 max_possible_force,
1013 ..
1014 }) => Some((force / max_possible_force) as f32),
1015 None => None,
1016 },
1017 },
1018 });
1019
1020 if context_pointer_touch_id.pointer_touch_id.is_none()
1023 || context_pointer_touch_id.pointer_touch_id.unwrap() == message.id
1024 {
1025 match message.phase {
1027 bevy_input::touch::TouchPhase::Started => {
1028 context_pointer_touch_id.pointer_touch_id = Some(message.id);
1029 egui_input_message_writer.write(EguiInputEvent {
1031 context,
1032 event: egui::Event::PointerMoved(pointer_position),
1033 });
1034 egui_input_message_writer.write(EguiInputEvent {
1036 context,
1037 event: egui::Event::PointerButton {
1038 pos: pointer_position,
1039 button: egui::PointerButton::Primary,
1040 pressed: true,
1041 modifiers,
1042 },
1043 });
1044 }
1045 bevy_input::touch::TouchPhase::Moved => {
1046 egui_input_message_writer.write(EguiInputEvent {
1047 context,
1048 event: egui::Event::PointerMoved(pointer_position),
1049 });
1050 }
1051 bevy_input::touch::TouchPhase::Ended => {
1052 context_pointer_touch_id.pointer_touch_id = None;
1053 egui_input_message_writer.write(EguiInputEvent {
1054 context,
1055 event: egui::Event::PointerButton {
1056 pos: pointer_position,
1057 button: egui::PointerButton::Primary,
1058 pressed: false,
1059 modifiers,
1060 },
1061 });
1062 egui_input_message_writer.write(EguiInputEvent {
1063 context,
1064 event: egui::Event::PointerGone,
1065 });
1066
1067 #[cfg(target_arch = "wasm32")]
1068 if !is_mobile_safari() {
1069 update_text_agent(
1070 _output.platform_output.ime.is_some()
1071 || _output.platform_output.mutable_text_under_cursor,
1072 );
1073 }
1074 }
1075 bevy_input::touch::TouchPhase::Canceled => {
1076 context_pointer_touch_id.pointer_touch_id = None;
1077 egui_input_message_writer.write(EguiInputEvent {
1078 context,
1079 event: egui::Event::PointerGone,
1080 });
1081 }
1082 }
1083 }
1084}
1085
1086#[allow(clippy::too_many_arguments)]
1088pub fn write_egui_input_system(
1089 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
1090 window_to_egui_context_map: Res<WindowToEguiContextMap>,
1091 modifier_keys_state: Res<ModifierKeysState>,
1092 mut egui_input_reader: MessageReader<EguiInputEvent>,
1093 mut egui_file_dnd_message_reader: MessageReader<EguiFileDragAndDropMessage>,
1094 mut egui_contexts: Query<(Entity, &mut EguiInput)>,
1095 windows: Query<&Window>,
1096 time: Res<Time<Real>>,
1097) {
1098 for EguiInputEvent { context, event } in egui_input_reader.read() {
1099 #[cfg(feature = "log_input_messages")]
1100 log::warn!("{context:?}: {message:?}");
1101
1102 let (_, mut egui_input) = match egui_contexts.get_mut(*context) {
1103 Ok(egui_input) => egui_input,
1104 Err(err) => {
1105 log::error!(
1106 "Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
1107 );
1108 continue;
1109 }
1110 };
1111
1112 egui_input.events.push(event.clone());
1113 }
1114
1115 for EguiFileDragAndDropMessage { context, message } in egui_file_dnd_message_reader.read() {
1116 #[cfg(feature = "log_file_dnd_messages")]
1117 log::warn!("{context:?}: {message:?}");
1118
1119 let (_, mut egui_input) = match egui_contexts.get_mut(*context) {
1120 Ok(egui_input) => egui_input,
1121 Err(err) => {
1122 log::error!(
1123 "Failed to get an Egui context ({context:?}) for an message ({message:?}): {err:?}"
1124 );
1125 continue;
1126 }
1127 };
1128
1129 match message {
1130 FileDragAndDrop::DroppedFile {
1131 window: _,
1132 path_buf,
1133 } => {
1134 egui_input.hovered_files.clear();
1135 egui_input.dropped_files.push(egui::DroppedFile {
1136 path: Some(path_buf.clone()),
1137 ..Default::default()
1138 });
1139 }
1140 FileDragAndDrop::HoveredFile {
1141 window: _,
1142 path_buf,
1143 } => {
1144 egui_input.hovered_files.push(egui::HoveredFile {
1145 path: Some(path_buf.clone()),
1146 ..Default::default()
1147 });
1148 }
1149 FileDragAndDrop::HoveredFileCanceled { window: _ } => {
1150 egui_input.hovered_files.clear();
1151 }
1152 }
1153 }
1154
1155 for (entity, mut egui_input) in egui_contexts.iter_mut() {
1156 egui_input.focused = focused_non_window_egui_context.as_deref().map_or_else(
1157 || {
1158 window_to_egui_context_map
1159 .context_to_window
1160 .get(&entity)
1161 .and_then(|window_entity| windows.get_some(*window_entity))
1162 .is_some_and(|window| window.focused)
1163 },
1164 |context| context.0 == entity,
1165 );
1166 egui_input.modifiers = modifier_keys_state.to_egui_modifiers();
1167 egui_input.time = Some(time.elapsed_secs_f64());
1168 }
1169}
1170
1171pub fn absorb_bevy_input_system(
1189 egui_wants_input: Res<EguiWantsInput>,
1190 mut mouse_input: ResMut<ButtonInput<MouseButton>>,
1191 mut keyboard_input: ResMut<ButtonInput<KeyCode>>,
1192 mut keyboard_input_messages: ResMut<Messages<KeyboardInput>>,
1193 mut mouse_wheel_messages: ResMut<Messages<MouseWheel>>,
1194 mut mouse_button_input_messages: ResMut<Messages<MouseButtonInput>>,
1195) {
1196 let modifiers = [
1197 KeyCode::SuperLeft,
1198 KeyCode::SuperRight,
1199 KeyCode::ControlLeft,
1200 KeyCode::ControlRight,
1201 KeyCode::AltLeft,
1202 KeyCode::AltRight,
1203 KeyCode::ShiftLeft,
1204 KeyCode::ShiftRight,
1205 ];
1206
1207 let pressed = modifiers.map(|key| keyboard_input.pressed(key).then_some(key));
1208
1209 if egui_wants_input.wants_any_keyboard_input() {
1212 keyboard_input.reset_all();
1213 keyboard_input_messages.clear();
1214 }
1215 if egui_wants_input.wants_any_pointer_input() {
1216 mouse_input.reset_all();
1217 mouse_wheel_messages.clear();
1218 mouse_button_input_messages.clear();
1219 }
1220
1221 for key in pressed.into_iter().flatten() {
1222 keyboard_input.press(key);
1223 }
1224}
1225
1226#[derive(Resource, Clone, Debug, Default)]
1228pub struct EguiWantsInput {
1229 is_pointer_over_area: bool,
1230 wants_pointer_input: bool,
1231 is_using_pointer: bool,
1232 wants_keyboard_input: bool,
1233 is_popup_open: bool,
1234}
1235
1236impl EguiWantsInput {
1237 pub fn is_pointer_over_area(&self) -> bool {
1239 self.is_pointer_over_area
1240 }
1241
1242 pub fn wants_pointer_input(&self) -> bool {
1249 self.wants_pointer_input
1250 }
1251
1252 pub fn is_using_pointer(&self) -> bool {
1256 self.is_using_pointer
1257 }
1258
1259 pub fn wants_keyboard_input(&self) -> bool {
1261 self.wants_keyboard_input
1262 }
1263
1264 #[deprecated = "use is_popup_open, renamed upstream in egui"]
1266 pub fn is_context_menu_open(&self) -> bool {
1267 self.is_popup_open
1268 }
1269
1270 pub fn is_popup_open(&self) -> bool {
1272 self.is_popup_open
1273 }
1274
1275 pub fn wants_any_pointer_input(&self) -> bool {
1278 self.is_pointer_over_area
1279 || self.wants_pointer_input
1280 || self.is_using_pointer
1281 || self.is_popup_open
1282 }
1283
1284 pub fn wants_any_keyboard_input(&self) -> bool {
1287 self.wants_keyboard_input || self.is_popup_open
1288 }
1289
1290 pub fn wants_any_input(&self) -> bool {
1293 self.wants_any_pointer_input() || self.wants_any_keyboard_input()
1294 }
1295
1296 fn reset(&mut self) {
1297 self.is_pointer_over_area = false;
1298 self.wants_pointer_input = false;
1299 self.is_using_pointer = false;
1300 self.wants_keyboard_input = false;
1301 self.is_popup_open = false;
1302 }
1303}
1304
1305pub fn write_egui_wants_input_system(
1307 mut egui_context_query: Query<&mut EguiContext>,
1308 mut egui_wants_input: ResMut<EguiWantsInput>,
1309) {
1310 egui_wants_input.reset();
1311
1312 for mut ctx in egui_context_query.iter_mut() {
1313 let egui_ctx = ctx.get_mut();
1314 egui_wants_input.is_pointer_over_area =
1315 egui_wants_input.is_pointer_over_area || egui_ctx.is_pointer_over_area();
1316 egui_wants_input.wants_pointer_input =
1317 egui_wants_input.wants_pointer_input || egui_ctx.wants_pointer_input();
1318 egui_wants_input.is_using_pointer =
1319 egui_wants_input.is_using_pointer || egui_ctx.is_using_pointer();
1320 egui_wants_input.wants_keyboard_input =
1321 egui_wants_input.wants_keyboard_input || egui_ctx.wants_keyboard_input();
1322 egui_wants_input.is_popup_open = egui_wants_input.is_popup_open || egui_ctx.is_popup_open();
1323 }
1324}
1325
1326pub fn egui_wants_any_pointer_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1329 egui_wants_input_resource.wants_any_pointer_input()
1330}
1331
1332pub fn egui_wants_any_keyboard_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1335 egui_wants_input_resource.wants_any_keyboard_input()
1336}
1337
1338pub fn egui_wants_any_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1341 egui_wants_input_resource.wants_any_input()
1342}