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::prelude::*;
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 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}
39
40#[derive(Event)]
41pub struct EguiInputEvent {
43 pub context: Entity,
45 pub event: egui::Event,
47}
48
49#[derive(Event)]
50pub struct EguiFileDragAndDropEvent {
52 pub context: Entity,
54 pub event: FileDragAndDrop,
56}
57
58#[derive(Resource)]
59pub struct HoveredNonWindowEguiContext(pub Entity);
65
66#[derive(Resource)]
80pub struct FocusedNonWindowEguiContext(pub Entity);
81
82#[derive(Resource, Clone, Copy, Debug)]
84pub struct ModifierKeysState {
85 pub shift: bool,
87 pub ctrl: bool,
89 pub alt: bool,
91 pub win: bool,
93 is_macos: bool,
94}
95
96impl Default for ModifierKeysState {
97 fn default() -> Self {
98 let mut state = Self {
99 shift: false,
100 ctrl: false,
101 alt: false,
102 win: false,
103 is_macos: false,
104 };
105
106 #[cfg(not(target_arch = "wasm32"))]
107 {
108 state.is_macos = cfg!(target_os = "macos");
109 }
110
111 #[cfg(target_arch = "wasm32")]
112 if let Some(window) = web_sys::window() {
113 let nav = window.navigator();
114 if let Ok(user_agent) = nav.user_agent() {
115 if user_agent.to_ascii_lowercase().contains("mac") {
116 state.is_macos = true;
117 }
118 }
119 }
120
121 state
122 }
123}
124
125impl ModifierKeysState {
126 pub fn to_egui_modifiers(&self) -> egui::Modifiers {
128 egui::Modifiers {
129 alt: self.alt,
130 ctrl: self.ctrl,
131 shift: self.shift,
132 mac_cmd: if self.is_macos { self.win } else { false },
133 command: if self.is_macos { self.win } else { self.ctrl },
134 }
135 }
136
137 pub fn text_input_is_allowed(&self) -> bool {
139 !self.win && !self.ctrl || !self.is_macos && self.ctrl && self.alt
141 }
142
143 fn reset(&mut self) {
144 self.shift = false;
145 self.ctrl = false;
146 self.alt = false;
147 self.win = false;
148 }
149}
150
151pub fn write_modifiers_keys_state_system(
153 mut ev_keyboard_input: EventReader<KeyboardInput>,
154 mut ev_focus: EventReader<KeyboardFocusLost>,
155 mut modifier_keys_state: ResMut<ModifierKeysState>,
156) {
157 if !ev_focus.is_empty() {
159 ev_focus.clear();
160 modifier_keys_state.reset();
161 }
162
163 for event in ev_keyboard_input.read() {
164 let KeyboardInput {
165 logical_key, state, ..
166 } = event;
167 match logical_key {
168 Key::Shift => {
169 modifier_keys_state.shift = state.is_pressed();
170 }
171 Key::Control => {
172 modifier_keys_state.ctrl = state.is_pressed();
173 }
174 Key::Alt => {
175 modifier_keys_state.alt = state.is_pressed();
176 }
177 Key::Super | Key::Meta => {
178 modifier_keys_state.win = state.is_pressed();
179 }
180 _ => {}
181 };
182 }
183}
184
185pub fn write_window_pointer_moved_events_system(
187 mut cursor_moved_reader: EventReader<CursorMoved>,
188 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
189 mut egui_contexts: Query<
190 (&EguiContextSettings, &mut EguiContextPointerPosition),
191 (With<EguiContext>, With<Window>),
192 >,
193) {
194 for event in cursor_moved_reader.read() {
195 let Some((context_settings, mut context_pointer_position)) =
196 egui_contexts.get_some_mut(event.window)
197 else {
198 continue;
199 };
200
201 if !context_settings
202 .input_system_settings
203 .run_write_window_pointer_moved_events_system
204 {
205 continue;
206 }
207
208 let scale_factor = context_settings.scale_factor;
209 let pointer_position = vec2_into_egui_pos2(event.position / scale_factor);
210 context_pointer_position.position = pointer_position;
211 egui_input_event_writer.write(EguiInputEvent {
212 context: event.window,
213 event: egui::Event::PointerMoved(pointer_position),
214 });
215 }
216}
217
218pub fn write_pointer_button_events_system(
221 egui_global_settings: Res<EguiGlobalSettings>,
222 mut commands: Commands,
223 hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
224 modifier_keys_state: Res<ModifierKeysState>,
225 mut mouse_button_input_reader: EventReader<MouseButtonInput>,
226 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
227 egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
228) {
229 let modifiers = modifier_keys_state.to_egui_modifiers();
230 for event in mouse_button_input_reader.read() {
231 let hovered_context = hovered_non_window_egui_context
232 .as_deref()
233 .map_or(event.window, |hovered| hovered.0);
234
235 let Some((context_settings, context_pointer_position)) =
236 egui_contexts.get_some(hovered_context)
237 else {
238 continue;
239 };
240
241 if !context_settings
242 .input_system_settings
243 .run_write_pointer_button_events_system
244 {
245 continue;
246 }
247
248 let button = match event.button {
249 MouseButton::Left => Some(egui::PointerButton::Primary),
250 MouseButton::Right => Some(egui::PointerButton::Secondary),
251 MouseButton::Middle => Some(egui::PointerButton::Middle),
252 MouseButton::Back => Some(egui::PointerButton::Extra1),
253 MouseButton::Forward => Some(egui::PointerButton::Extra2),
254 _ => None,
255 };
256 let Some(button) = button else {
257 continue;
258 };
259 let pressed = match event.state {
260 ButtonState::Pressed => true,
261 ButtonState::Released => false,
262 };
263 egui_input_event_writer.write(EguiInputEvent {
264 context: hovered_context,
265 event: egui::Event::PointerButton {
266 pos: context_pointer_position.position,
267 button,
268 pressed,
269 modifiers,
270 },
271 });
272
273 if egui_global_settings.enable_focused_non_window_context_updates && pressed {
275 if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
276 commands.insert_resource(FocusedNonWindowEguiContext(
277 hovered_non_window_egui_context.0,
278 ));
279 } else {
280 commands.remove_resource::<FocusedNonWindowEguiContext>();
281 }
282 }
283 }
284}
285
286pub fn write_non_window_pointer_moved_events_system(
288 hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
289 mut cursor_moved_reader: EventReader<CursorMoved>,
290 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
291 egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
292) {
293 if cursor_moved_reader.is_empty() {
294 return;
295 }
296
297 cursor_moved_reader.clear();
298 let Some(HoveredNonWindowEguiContext(hovered_non_window_egui_context)) =
299 hovered_non_window_egui_context.as_deref()
300 else {
301 return;
302 };
303
304 let Some((context_settings, context_pointer_position)) =
305 egui_contexts.get_some(*hovered_non_window_egui_context)
306 else {
307 return;
308 };
309
310 if !context_settings
311 .input_system_settings
312 .run_write_non_window_pointer_moved_events_system
313 {
314 return;
315 }
316
317 egui_input_event_writer.write(EguiInputEvent {
318 context: *hovered_non_window_egui_context,
319 event: egui::Event::PointerMoved(context_pointer_position.position),
320 });
321}
322
323pub fn write_mouse_wheel_events_system(
325 modifier_keys_state: Res<ModifierKeysState>,
326 hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
327 mut mouse_wheel_reader: EventReader<MouseWheel>,
328 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
329 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
330) {
331 let modifiers = modifier_keys_state.to_egui_modifiers();
332 for event in mouse_wheel_reader.read() {
333 let delta = egui::vec2(event.x, event.y);
334 let unit = match event.unit {
335 MouseScrollUnit::Line => egui::MouseWheelUnit::Line,
336 MouseScrollUnit::Pixel => egui::MouseWheelUnit::Point,
337 };
338
339 let context = hovered_non_window_egui_context
340 .as_deref()
341 .map_or(event.window, |hovered| hovered.0);
342
343 let Some(context_settings) = egui_contexts.get_some(context) else {
344 continue;
345 };
346
347 if !context_settings
348 .input_system_settings
349 .run_write_mouse_wheel_events_system
350 {
351 continue;
352 }
353
354 egui_input_event_writer.write(EguiInputEvent {
355 context,
356 event: egui::Event::MouseWheel {
357 unit,
358 delta,
359 modifiers,
360 },
361 });
362 }
363}
364
365pub fn write_keyboard_input_events_system(
367 modifier_keys_state: Res<ModifierKeysState>,
368 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
369 #[cfg(all(
370 feature = "manage_clipboard",
371 not(target_os = "android"),
372 not(target_arch = "wasm32")
373 ))]
374 mut egui_clipboard: ResMut<crate::EguiClipboard>,
375 mut keyboard_input_reader: EventReader<KeyboardInput>,
376 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
377 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
378) {
379 let modifiers = modifier_keys_state.to_egui_modifiers();
380 for event in keyboard_input_reader.read() {
381 let context = focused_non_window_egui_context
382 .as_deref()
383 .map_or(event.window, |context| context.0);
384
385 let Some(context_settings) = egui_contexts.get_some(context) else {
386 continue;
387 };
388
389 if !context_settings
390 .input_system_settings
391 .run_write_keyboard_input_events_system
392 {
393 continue;
394 }
395
396 if modifier_keys_state.text_input_is_allowed() && event.state.is_pressed() {
397 match &event.logical_key {
398 Key::Character(char) if char.matches(char::is_control).count() == 0 => {
399 egui_input_event_writer.write(EguiInputEvent {
400 context,
401 event: egui::Event::Text(char.to_string()),
402 });
403 }
404 Key::Space => {
405 egui_input_event_writer.write(EguiInputEvent {
406 context,
407 event: egui::Event::Text(" ".to_string()),
408 });
409 }
410 _ => (),
411 }
412 }
413
414 let key = crate::helpers::bevy_to_egui_key(&event.logical_key);
415 let physical_key = crate::helpers::bevy_to_egui_physical_key(&event.key_code);
416
417 let (Some(key), physical_key) = (key.or(physical_key), physical_key) else {
420 continue;
421 };
422
423 let egui_event = egui::Event::Key {
424 key,
425 pressed: event.state.is_pressed(),
426 repeat: false,
427 modifiers,
428 physical_key,
429 };
430 egui_input_event_writer.write(EguiInputEvent {
431 context,
432 event: egui_event,
433 });
434
435 #[cfg(all(
438 feature = "manage_clipboard",
439 not(target_os = "android"),
440 not(target_arch = "wasm32")
441 ))]
442 if modifiers.command && event.state.is_pressed() {
443 match key {
444 egui::Key::C => {
445 egui_input_event_writer.write(EguiInputEvent {
446 context,
447 event: egui::Event::Copy,
448 });
449 }
450 egui::Key::X => {
451 egui_input_event_writer.write(EguiInputEvent {
452 context,
453 event: egui::Event::Cut,
454 });
455 }
456 egui::Key::V => {
457 if let Some(contents) = egui_clipboard.get_text() {
458 egui_input_event_writer.write(EguiInputEvent {
459 context,
460 event: egui::Event::Text(contents),
461 });
462 }
463 }
464 _ => {}
465 }
466 }
467 }
468}
469
470pub fn write_ime_events_system(
472 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
473 mut ime_reader: EventReader<Ime>,
474 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
475 mut egui_contexts: Query<
476 (
477 Entity,
478 &EguiContextSettings,
479 &mut EguiContextImeState,
480 &EguiOutput,
481 ),
482 With<EguiContext>,
483 >,
484) {
485 for event in ime_reader.read() {
486 let window = match &event {
487 Ime::Preedit { window, .. }
488 | Ime::Commit { window, .. }
489 | Ime::Disabled { window }
490 | Ime::Enabled { window } => *window,
491 };
492 let context = focused_non_window_egui_context
493 .as_deref()
494 .map_or(window, |context| context.0);
495
496 let Some((_entity, context_settings, mut ime_state, _egui_output)) =
497 egui_contexts.get_some_mut(context)
498 else {
499 continue;
500 };
501
502 if !context_settings
503 .input_system_settings
504 .run_write_ime_events_system
505 {
506 continue;
507 }
508
509 let ime_event_enable =
510 |ime_state: &mut EguiContextImeState,
511 egui_input_event_writer: &mut EventWriter<EguiInputEvent>| {
512 if !ime_state.has_sent_ime_enabled {
513 egui_input_event_writer.write(EguiInputEvent {
514 context,
515 event: egui::Event::Ime(egui::ImeEvent::Enabled),
516 });
517 ime_state.has_sent_ime_enabled = true;
518 }
519 };
520
521 let ime_event_disable =
522 |ime_state: &mut EguiContextImeState,
523 egui_input_event_writer: &mut EventWriter<EguiInputEvent>| {
524 if !ime_state.has_sent_ime_enabled {
525 egui_input_event_writer.write(EguiInputEvent {
526 context,
527 event: egui::Event::Ime(egui::ImeEvent::Disabled),
528 });
529 ime_state.has_sent_ime_enabled = false;
530 }
531 };
532
533 match event {
535 Ime::Enabled { window: _ } => {
536 ime_event_enable(&mut ime_state, &mut egui_input_event_writer);
537 }
538 Ime::Preedit {
539 value,
540 window: _,
541 cursor: _,
542 } => {
543 ime_event_enable(&mut ime_state, &mut egui_input_event_writer);
544 egui_input_event_writer.write(EguiInputEvent {
545 context,
546 event: egui::Event::Ime(egui::ImeEvent::Preedit(value.clone())),
547 });
548 }
549 Ime::Commit { value, window: _ } => {
550 egui_input_event_writer.write(EguiInputEvent {
551 context,
552 event: egui::Event::Ime(egui::ImeEvent::Commit(value.clone())),
553 });
554 ime_event_disable(&mut ime_state, &mut egui_input_event_writer);
555 }
556 Ime::Disabled { window: _ } => {
557 ime_event_disable(&mut ime_state, &mut egui_input_event_writer);
558 }
559 }
560 }
561}
562
563pub fn write_file_dnd_events_system(
565 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
566 mut dnd_reader: EventReader<FileDragAndDrop>,
567 mut egui_file_dnd_event_writer: EventWriter<EguiFileDragAndDropEvent>,
568 egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
569) {
570 for event in dnd_reader.read() {
571 let window = match &event {
572 FileDragAndDrop::DroppedFile { window, .. }
573 | FileDragAndDrop::HoveredFile { window, .. }
574 | FileDragAndDrop::HoveredFileCanceled { window } => *window,
575 };
576 let context = focused_non_window_egui_context
577 .as_deref()
578 .map_or(window, |context| context.0);
579
580 let Some(context_settings) = egui_contexts.get_some(context) else {
581 continue;
582 };
583
584 if !context_settings
585 .input_system_settings
586 .run_write_file_dnd_events_system
587 {
588 continue;
589 }
590
591 match event {
592 FileDragAndDrop::DroppedFile { window, path_buf } => {
593 egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
594 context,
595 event: FileDragAndDrop::DroppedFile {
596 window: *window,
597 path_buf: path_buf.clone(),
598 },
599 });
600 }
601 FileDragAndDrop::HoveredFile { window, path_buf } => {
602 egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
603 context,
604 event: FileDragAndDrop::HoveredFile {
605 window: *window,
606 path_buf: path_buf.clone(),
607 },
608 });
609 }
610 FileDragAndDrop::HoveredFileCanceled { window } => {
611 egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
612 context,
613 event: FileDragAndDrop::HoveredFileCanceled { window: *window },
614 });
615 }
616 }
617 }
618}
619
620pub fn write_window_touch_events_system(
622 mut commands: Commands,
623 egui_global_settings: Res<EguiGlobalSettings>,
624 hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
625 modifier_keys_state: Res<ModifierKeysState>,
626 mut touch_input_reader: EventReader<TouchInput>,
627 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
628 mut egui_contexts: Query<
629 (
630 &EguiContextSettings,
631 &mut EguiContextPointerPosition,
632 &mut EguiContextPointerTouchId,
633 &EguiOutput,
634 ),
635 (With<EguiContext>, With<Window>),
636 >,
637) {
638 let modifiers = modifier_keys_state.to_egui_modifiers();
639 for event in touch_input_reader.read() {
640 let Some((
641 context_settings,
642 mut context_pointer_position,
643 mut context_pointer_touch_id,
644 output,
645 )) = egui_contexts.get_some_mut(event.window)
646 else {
647 continue;
648 };
649
650 if egui_global_settings.enable_focused_non_window_context_updates {
651 if let bevy_input::touch::TouchPhase::Started = event.phase {
652 if let Some(hovered_non_window_egui_context) =
653 hovered_non_window_egui_context.as_deref()
654 {
655 if let bevy_input::touch::TouchPhase::Started = event.phase {
656 commands.insert_resource(FocusedNonWindowEguiContext(
657 hovered_non_window_egui_context.0,
658 ));
659 }
660
661 continue;
662 }
663
664 commands.remove_resource::<FocusedNonWindowEguiContext>();
665 }
666 }
667
668 if !context_settings
669 .input_system_settings
670 .run_write_window_touch_events_system
671 {
672 continue;
673 }
674
675 let scale_factor = context_settings.scale_factor;
676 let touch_position = vec2_into_egui_pos2(event.position / scale_factor);
677 context_pointer_position.position = touch_position;
678 write_touch_event(
679 &mut egui_input_event_writer,
680 event,
681 event.window,
682 output,
683 touch_position,
684 modifiers,
685 &mut context_pointer_touch_id,
686 );
687 }
688}
689
690pub fn write_non_window_touch_events_system(
692 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
693 mut touch_input_reader: EventReader<TouchInput>,
694 mut egui_input_event_writer: EventWriter<EguiInputEvent>,
695 modifier_keys_state: Res<ModifierKeysState>,
696 mut egui_contexts: Query<
697 (
698 &EguiContextSettings,
699 &EguiContextPointerPosition,
700 &mut EguiContextPointerTouchId,
701 &EguiOutput,
702 ),
703 With<EguiContext>,
704 >,
705) {
706 let modifiers = modifier_keys_state.to_egui_modifiers();
707 for event in touch_input_reader.read() {
708 let Some(&FocusedNonWindowEguiContext(focused_non_window_egui_context)) =
709 focused_non_window_egui_context.as_deref()
710 else {
711 continue;
712 };
713
714 let Some((
715 context_settings,
716 context_pointer_position,
717 mut context_pointer_touch_id,
718 output,
719 )) = egui_contexts.get_some_mut(focused_non_window_egui_context)
720 else {
721 continue;
722 };
723
724 if !context_settings
725 .input_system_settings
726 .run_write_non_window_touch_events_system
727 {
728 continue;
729 }
730
731 write_touch_event(
732 &mut egui_input_event_writer,
733 event,
734 focused_non_window_egui_context,
735 output,
736 context_pointer_position.position,
737 modifiers,
738 &mut context_pointer_touch_id,
739 );
740 }
741}
742
743fn write_touch_event(
744 egui_input_event_writer: &mut EventWriter<EguiInputEvent>,
745 event: &TouchInput,
746 context: Entity,
747 _output: &EguiOutput,
748 pointer_position: egui::Pos2,
749 modifiers: Modifiers,
750 context_pointer_touch_id: &mut EguiContextPointerTouchId,
751) {
752 let touch_id = egui::TouchId::from(event.id);
753
754 egui_input_event_writer.write(EguiInputEvent {
756 context,
757 event: egui::Event::Touch {
758 device_id: egui::TouchDeviceId(event.window.to_bits()),
759 id: touch_id,
760 phase: match event.phase {
761 bevy_input::touch::TouchPhase::Started => egui::TouchPhase::Start,
762 bevy_input::touch::TouchPhase::Moved => egui::TouchPhase::Move,
763 bevy_input::touch::TouchPhase::Ended => egui::TouchPhase::End,
764 bevy_input::touch::TouchPhase::Canceled => egui::TouchPhase::Cancel,
765 },
766 pos: pointer_position,
767 force: match event.force {
768 Some(bevy_input::touch::ForceTouch::Normalized(force)) => Some(force as f32),
769 Some(bevy_input::touch::ForceTouch::Calibrated {
770 force,
771 max_possible_force,
772 ..
773 }) => Some((force / max_possible_force) as f32),
774 None => None,
775 },
776 },
777 });
778
779 if context_pointer_touch_id.pointer_touch_id.is_none()
782 || context_pointer_touch_id.pointer_touch_id.unwrap() == event.id
783 {
784 match event.phase {
786 bevy_input::touch::TouchPhase::Started => {
787 context_pointer_touch_id.pointer_touch_id = Some(event.id);
788 egui_input_event_writer.write(EguiInputEvent {
790 context,
791 event: egui::Event::PointerMoved(pointer_position),
792 });
793 egui_input_event_writer.write(EguiInputEvent {
795 context,
796 event: egui::Event::PointerButton {
797 pos: pointer_position,
798 button: egui::PointerButton::Primary,
799 pressed: true,
800 modifiers,
801 },
802 });
803 }
804 bevy_input::touch::TouchPhase::Moved => {
805 egui_input_event_writer.write(EguiInputEvent {
806 context,
807 event: egui::Event::PointerMoved(pointer_position),
808 });
809 }
810 bevy_input::touch::TouchPhase::Ended => {
811 context_pointer_touch_id.pointer_touch_id = None;
812 egui_input_event_writer.write(EguiInputEvent {
813 context,
814 event: egui::Event::PointerButton {
815 pos: pointer_position,
816 button: egui::PointerButton::Primary,
817 pressed: false,
818 modifiers,
819 },
820 });
821 egui_input_event_writer.write(EguiInputEvent {
822 context,
823 event: egui::Event::PointerGone,
824 });
825
826 #[cfg(target_arch = "wasm32")]
827 if !is_mobile_safari() {
828 update_text_agent(
829 _output.platform_output.ime.is_some()
830 || _output.platform_output.mutable_text_under_cursor,
831 );
832 }
833 }
834 bevy_input::touch::TouchPhase::Canceled => {
835 context_pointer_touch_id.pointer_touch_id = None;
836 egui_input_event_writer.write(EguiInputEvent {
837 context,
838 event: egui::Event::PointerGone,
839 });
840 }
841 }
842 }
843}
844
845pub fn write_egui_input_system(
847 focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
848 modifier_keys_state: Res<ModifierKeysState>,
849 mut egui_input_event_reader: EventReader<EguiInputEvent>,
850 mut egui_file_dnd_event_reader: EventReader<EguiFileDragAndDropEvent>,
851 mut egui_contexts: Query<(Entity, &mut EguiInput, Option<&Window>)>,
852 time: Res<Time<Real>>,
853) {
854 for EguiInputEvent { context, event } in egui_input_event_reader.read() {
855 #[cfg(feature = "log_input_events")]
856 log::warn!("{context:?}: {event:?}");
857
858 let (_, mut egui_input, _) = match egui_contexts.get_mut(*context) {
859 Ok(egui_input) => egui_input,
860 Err(err) => {
861 log::error!(
862 "Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
863 );
864 continue;
865 }
866 };
867
868 egui_input.events.push(event.clone());
869 }
870
871 for EguiFileDragAndDropEvent { context, event } in egui_file_dnd_event_reader.read() {
872 #[cfg(feature = "log_file_dnd_events")]
873 log::warn!("{context:?}: {event:?}");
874
875 let (_, mut egui_input, _) = match egui_contexts.get_mut(*context) {
876 Ok(egui_input) => egui_input,
877 Err(err) => {
878 log::error!(
879 "Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
880 );
881 continue;
882 }
883 };
884
885 match event {
886 FileDragAndDrop::DroppedFile {
887 window: _,
888 path_buf,
889 } => {
890 egui_input.hovered_files.clear();
891 egui_input.dropped_files.push(egui::DroppedFile {
892 path: Some(path_buf.clone()),
893 ..Default::default()
894 });
895 }
896 FileDragAndDrop::HoveredFile {
897 window: _,
898 path_buf,
899 } => {
900 egui_input.hovered_files.push(egui::HoveredFile {
901 path: Some(path_buf.clone()),
902 ..Default::default()
903 });
904 }
905 FileDragAndDrop::HoveredFileCanceled { window: _ } => {
906 egui_input.hovered_files.clear();
907 }
908 }
909 }
910
911 for (entity, mut egui_input, window) in egui_contexts.iter_mut() {
912 egui_input.focused = focused_non_window_egui_context.as_deref().map_or_else(
913 || window.is_some_and(|window| window.focused),
914 |context| context.0 == entity,
915 );
916 egui_input.modifiers = modifier_keys_state.to_egui_modifiers();
917 egui_input.time = Some(time.elapsed_secs_f64());
918 }
919}
920
921pub fn absorb_bevy_input_system(
939 egui_wants_input: Res<EguiWantsInput>,
940 mut mouse_input: ResMut<ButtonInput<MouseButton>>,
941 mut keyboard_input: ResMut<ButtonInput<KeyCode>>,
942 mut keyboard_input_events: ResMut<Events<KeyboardInput>>,
943 mut mouse_wheel_events: ResMut<Events<MouseWheel>>,
944 mut mouse_button_input_events: ResMut<Events<MouseButtonInput>>,
945) {
946 let modifiers = [
947 KeyCode::SuperLeft,
948 KeyCode::SuperRight,
949 KeyCode::ControlLeft,
950 KeyCode::ControlRight,
951 KeyCode::AltLeft,
952 KeyCode::AltRight,
953 KeyCode::ShiftLeft,
954 KeyCode::ShiftRight,
955 ];
956
957 let pressed = modifiers.map(|key| keyboard_input.pressed(key).then_some(key));
958
959 if egui_wants_input.wants_any_keyboard_input() {
962 keyboard_input.reset_all();
963 keyboard_input_events.clear();
964 }
965 if egui_wants_input.wants_any_pointer_input() {
966 mouse_input.reset_all();
967 mouse_wheel_events.clear();
968 mouse_button_input_events.clear();
969 }
970
971 for key in pressed.into_iter().flatten() {
972 keyboard_input.press(key);
973 }
974}
975
976#[derive(Resource, Clone, Debug, Default)]
978pub struct EguiWantsInput {
979 is_pointer_over_area: bool,
980 wants_pointer_input: bool,
981 is_using_pointer: bool,
982 wants_keyboard_input: bool,
983 is_context_menu_open: bool,
984}
985
986impl EguiWantsInput {
987 pub fn is_pointer_over_area(&self) -> bool {
989 self.is_pointer_over_area
990 }
991
992 pub fn wants_pointer_input(&self) -> bool {
999 self.wants_pointer_input
1000 }
1001
1002 pub fn is_using_pointer(&self) -> bool {
1006 self.is_using_pointer
1007 }
1008
1009 pub fn wants_keyboard_input(&self) -> bool {
1011 self.wants_keyboard_input
1012 }
1013
1014 pub fn is_context_menu_open(&self) -> bool {
1016 self.is_context_menu_open
1017 }
1018
1019 pub fn wants_any_pointer_input(&self) -> bool {
1022 self.is_pointer_over_area
1023 || self.wants_pointer_input
1024 || self.is_using_pointer
1025 || self.is_context_menu_open
1026 }
1027
1028 pub fn wants_any_keyboard_input(&self) -> bool {
1031 self.wants_keyboard_input || self.is_context_menu_open
1032 }
1033
1034 pub fn wants_any_input(&self) -> bool {
1037 self.wants_any_pointer_input() || self.wants_any_keyboard_input()
1038 }
1039
1040 fn reset(&mut self) {
1041 self.is_pointer_over_area = false;
1042 self.wants_pointer_input = false;
1043 self.is_using_pointer = false;
1044 self.wants_keyboard_input = false;
1045 self.is_context_menu_open = false;
1046 }
1047}
1048
1049pub fn write_egui_wants_input_system(
1051 mut egui_context_query: Query<&mut EguiContext>,
1052 mut egui_wants_input: ResMut<EguiWantsInput>,
1053) {
1054 egui_wants_input.reset();
1055
1056 for mut ctx in egui_context_query.iter_mut() {
1057 let egui_ctx = ctx.get_mut();
1058 egui_wants_input.is_pointer_over_area =
1059 egui_wants_input.is_pointer_over_area || egui_ctx.is_pointer_over_area();
1060 egui_wants_input.wants_pointer_input =
1061 egui_wants_input.wants_pointer_input || egui_ctx.wants_pointer_input();
1062 egui_wants_input.is_using_pointer =
1063 egui_wants_input.is_using_pointer || egui_ctx.is_using_pointer();
1064 egui_wants_input.wants_keyboard_input =
1065 egui_wants_input.wants_keyboard_input || egui_ctx.wants_keyboard_input();
1066 egui_wants_input.is_context_menu_open =
1067 egui_wants_input.is_context_menu_open || egui_ctx.is_context_menu_open();
1068 }
1069}
1070
1071pub fn egui_wants_any_pointer_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1074 egui_wants_input_resource.wants_any_pointer_input()
1075}
1076
1077pub fn egui_wants_any_keyboard_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1080 egui_wants_input_resource.wants_any_keyboard_input()
1081}
1082
1083pub fn egui_wants_any_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1086 egui_wants_input_resource.wants_any_input()
1087}