bevy_egui/
input.rs

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/// Cached pointer position, used to populate [`egui::Event::PointerButton`] events.
20#[derive(Component, Default)]
21pub struct EguiContextPointerPosition {
22    /// Pointer position.
23    pub position: egui::Pos2,
24}
25
26/// Stores an active touch id.
27#[derive(Component, Default)]
28pub struct EguiContextPointerTouchId {
29    /// Active touch id.
30    pub pointer_touch_id: Option<u64>,
31}
32
33/// Indicates whether [IME](https://en.wikipedia.org/wiki/Input_method) is enabled or disabled to avoid sending event duplicates.
34#[derive(Component, Default)]
35pub struct EguiContextImeState {
36    /// Indicates whether IME is enabled.
37    pub has_sent_ime_enabled: bool,
38    /// Indicates whether IME is currently allowed, i.e. if the virtual keyboard is shown.
39    pub is_ime_allowed: bool,
40}
41
42#[derive(Event)]
43/// Wraps Egui events emitted by [`crate::EguiInputSet`] systems.
44pub struct EguiInputEvent {
45    /// Context to pass an event to.
46    pub context: Entity,
47    /// Wrapped event.
48    pub event: egui::Event,
49}
50
51#[derive(Event)]
52/// Wraps [`bevy::FileDragAndDrop`](bevy_window::FileDragAndDrop) events emitted by [`crate::EguiInputSet`] systems.
53pub struct EguiFileDragAndDropEvent {
54    /// Context to pass an event to.
55    pub context: Entity,
56    /// Wrapped event.
57    pub event: FileDragAndDrop,
58}
59
60#[derive(Resource, Clone)]
61/// Insert this resource when a pointer hovers over a non-window (e.g. world-space) [`EguiContext`] entity.
62/// Also, make sure to update an [`EguiContextPointerPosition`] component of a hovered entity.
63/// Both updates should happen during [`crate::EguiInputSet::InitReading`].
64///
65/// To learn how `bevy_egui` uses this resource, see the [`FocusedNonWindowEguiContext`] documentation.
66pub struct HoveredNonWindowEguiContext(pub Entity);
67
68/// Stores an entity of a focused non-window context (to push keyboard events to).
69///
70/// The resource won't exist if no context is focused, [`Option<Res<FocusedNonWindowEguiContext>>`] must be used to read from it.
71/// If the [`HoveredNonWindowEguiContext`] resource exists, the [`FocusedNonWindowEguiContext`]
72/// resource will get inserted on mouse button press or touch start event
73/// (and removed if no hovered non-window context exists respectively).
74///
75/// Atm, it's up to users to update [`HoveredNonWindowEguiContext`] and [`EguiContextPointerPosition`].
76/// We might be able to add proper `bevy_picking` support for world space UI once [`bevy_picking::backend::HitData`]
77/// starts exposing triangle index or UV.
78///
79/// Updating focused contexts happens during [`crate::EguiInputSet::FocusContext`],
80/// see [`write_pointer_button_events_system`] and [`write_window_touch_events_system`].
81#[derive(Resource, Clone)]
82pub struct FocusedNonWindowEguiContext(pub Entity);
83
84/// Stores "pressed" state of modifier keys.
85#[derive(Resource, Clone, Copy, Debug)]
86pub struct ModifierKeysState {
87    /// Indicates whether the [`Key::Shift`] key is pressed.
88    pub shift: bool,
89    /// Indicates whether the [`Key::Control`] key is pressed.
90    pub ctrl: bool,
91    /// Indicates whether the [`Key::Alt`] key is pressed.
92    pub alt: bool,
93    /// Indicates whether the [`Key::Super`] (or [`Key::Meta`]) key is pressed.
94    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    /// Converts the struct to [`egui::Modifiers`].
129    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    /// Returns `true` if modifiers shouldn't prevent text input (we don't want to put characters on pressing Ctrl+A, etc).
140    pub fn text_input_is_allowed(&self) -> bool {
141        // Ctrl + Alt enables AltGr which is used to print special characters.
142        !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)]
154/// A bidirectional map between [`Window`] and [`EguiContext`] entities.
155/// Multiple contexts may belong to a single window.
156pub struct WindowToEguiContextMap {
157    /// Indexes contexts by windows.
158    pub window_to_contexts:
159        bevy_platform::collections::HashMap<Entity, bevy_platform::collections::HashSet<Entity>>,
160    /// Indexes windows by contexts.
161    pub context_to_window: bevy_platform::collections::HashMap<Entity, Entity>,
162}
163
164#[cfg(feature = "render")]
165impl WindowToEguiContextMap {
166    /// Adds a context to the map on creation.
167    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                // The resource doesn't exist in the headless mode.
188                let event_loop_proxy = (*event_loop_proxy).clone();
189                egui_context
190                    .get_mut()
191                    .set_request_repaint_callback(move |repaint_info| {
192                        // TODO: find a lightweight async timer implementation that also works in WASM
193                        //  to support non-zero wake-ups as well.
194                        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    /// Removes a context from the map on removal.
204    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
225/// Iterates over pairs of `(Event, Entity)`, where the entity points to the context that the event is related to.
226pub 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)]
266/// A helper system param to iterate over pairs of events and Egui contexts, see [`EguiContextsEventIterator`].
267pub 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    /// Returns [`EguiContextsEventIterator`] that iterates only over window events (i.e. skips contexts that render to images, etc.),
276    /// expects a lambda that extracts a window id from an event.
277    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    /// Returns [`EguiContextsEventIterator`] that iterates over window events but might substitute contexts with a currently hovered non-window context (see [`HoveredNonWindowEguiContext`]), expects a lambda that extracts a window id from an event.
296    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    /// Returns [`EguiContextsEventIterator`] that iterates over window events but might substitute contexts with a currently focused non-window context (see [`FocusedNonWindowEguiContext`]), expects a lambda that extracts a window id from an event.
318    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
340/// Reads [`KeyboardInput`] events to update the [`ModifierKeysState`] resource.
341pub 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 window focus is lost, clear all modifiers to avoid stuck keys.
347    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
374/// Reads [`MouseButtonInput`] events and wraps them into [`EguiInputEvent`] (only for window contexts).
375pub 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
407/// Reads [`MouseButtonInput`] events and wraps them into [`EguiInputEvent`], can redirect events to [`HoveredNonWindowEguiContext`],
408/// inserts, updates or removes the [`FocusedNonWindowEguiContext`] resource based on a hovered context.
409pub 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 we are hovering over some UI in world space, we want to mark it as focused on mouse click.
463        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
475/// Reads [`CursorMoved`] events and wraps them into [`EguiInputEvent`] for a [`HoveredNonWindowEguiContext`] context (if one exists).
476pub 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
512/// Reads [`MouseWheel`] events and wraps them into [`EguiInputEvent`], can redirect events to [`HoveredNonWindowEguiContext`].
513pub 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
549/// Reads [`KeyboardInput`] events and wraps them into [`EguiInputEvent`], can redirect events to [`FocusedNonWindowEguiContext`].
550pub 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        // "Logical OR physical key" is a fallback mechanism for keyboard layouts without Latin characters
598        // See: https://github.com/emilk/egui/blob/66c73b9cbfbd4d44489fc6f6a840d7d82bc34389/crates/egui-winit/src/lib.rs#L760
599        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        // We also check that it's a `ButtonState::Pressed` event, as we don't want to
616        // copy, cut or paste on the key release.
617        #[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
650/// Reads [`Ime`] events and wraps them into [`EguiInputEvent`], can redirect events to [`FocusedNonWindowEguiContext`].
651pub 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        // Aligned with the egui-winit implementation: https://github.com/emilk/egui/blob/0f2b427ff4c0a8c68f6622ec7d0afb7ba7e71bba/crates/egui-winit/src/lib.rs#L348
708        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/// Show the virtual keyboard when a text input is focused.
738/// Works by reading [`EguiOutput`] and calling `Window::set_ime_allowed` if the `ime` field is set.
739#[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    // We are on mobile, so we expect a single window.
746    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
770/// Reads [`FileDragAndDrop`] events and wraps them into [`EguiFileDragAndDropEvent`], can redirect events to [`HoveredNonWindowEguiContext`].
771pub 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
821/// Reads [`TouchInput`] events and wraps them into [`EguiInputEvent`].
822pub 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
893/// Reads [`TouchInput`] events and wraps them into [`EguiInputEvent`] for a [`HoveredNonWindowEguiContext`] context (if one exists).
894pub 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    // Emit the touch event.
958    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 we're not yet translating a touch, or we're translating this very
983    // touch, …
984    if context_pointer_touch_id.pointer_touch_id.is_none()
985        || context_pointer_touch_id.pointer_touch_id.unwrap() == event.id
986    {
987        // … emit PointerButton resp. PointerMoved events to emulate mouse.
988        match event.phase {
989            bevy_input::touch::TouchPhase::Started => {
990                context_pointer_touch_id.pointer_touch_id = Some(event.id);
991                // First move the pointer to the right location.
992                egui_input_event_writer.write(EguiInputEvent {
993                    context,
994                    event: egui::Event::PointerMoved(pointer_position),
995                });
996                // Then do mouse button input.
997                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/// Reads both [`EguiFileDragAndDropEvent`] and [`EguiInputEvent`] events and feeds them to Egui.
1049#[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
1133/// Clears Bevy input event buffers and resets [`ButtonInput`] resources if Egui
1134/// is using pointer or keyboard (see the [`write_egui_wants_input_system`] run condition).
1135///
1136/// This system isn't run by default, set [`EguiGlobalSettings::enable_absorb_bevy_input_system`]
1137/// to `true` to enable it.
1138///
1139/// ## Considerations
1140///
1141/// Enabling this system makes an assumption that `bevy_egui` takes priority in input handling
1142/// over other plugins and systems. This should work ok as long as there's no other system
1143/// clearing events the same way that might be in conflict with `bevy_egui`, and there's
1144/// no other system that needs a non-interrupted flow of events.
1145///
1146/// ## Alternative
1147///
1148/// A safer alternative is to apply `run_if(not(egui_wants_any_pointer_input))` or `run_if(not(egui_wants_any_keyboard_input))` to your systems
1149/// that need to be disabled while Egui is using input (see the [`egui_wants_any_pointer_input`], [`egui_wants_any_keyboard_input`] run conditions).
1150pub 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    // TODO: the list of events is definitely not comprehensive, but it should at least cover
1172    //  the most popular use-cases. We can add more on request.
1173    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/// Stores whether there's an Egui context using pointer or keyboard.
1189#[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    /// Is the pointer (mouse/touch) over any egui area?
1200    pub fn is_pointer_over_area(&self) -> bool {
1201        self.is_pointer_over_area
1202    }
1203
1204    /// True if egui is currently interested in the pointer (mouse or touch).
1205    ///
1206    /// Could be the pointer is hovering over a [`egui::Window`] or the user is dragging a widget.
1207    /// If `false`, the pointer is outside of any egui area and so
1208    /// you may be interested in what it is doing (e.g. controlling your game).
1209    /// Returns `false` if a drag started outside of egui and then moved over an egui area.
1210    pub fn wants_pointer_input(&self) -> bool {
1211        self.wants_pointer_input
1212    }
1213
1214    /// Is egui currently using the pointer position (e.g. dragging a slider)?
1215    ///
1216    /// NOTE: this will return `false` if the pointer is just hovering over an egui area.
1217    pub fn is_using_pointer(&self) -> bool {
1218        self.is_using_pointer
1219    }
1220
1221    /// If `true`, egui is currently listening on text input (e.g. typing text in a [`egui::TextEdit`]).
1222    pub fn wants_keyboard_input(&self) -> bool {
1223        self.wants_keyboard_input
1224    }
1225
1226    /// Is an egui context menu open?
1227    #[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    /// Is an egui context menu open?
1233    pub fn is_popup_open(&self) -> bool {
1234        self.is_popup_open
1235    }
1236
1237    /// Returns `true` if any of the following is true:
1238    /// [`EguiWantsInput::is_pointer_over_area`], [`EguiWantsInput::wants_pointer_input`], [`EguiWantsInput::is_using_pointer`], [`EguiWantsInput::is_context_menu_open`].
1239    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    /// Returns `true` if any of the following is true:
1247    /// [`EguiWantsInput::wants_keyboard_input`], [`EguiWantsInput::is_context_menu_open`].
1248    pub fn wants_any_keyboard_input(&self) -> bool {
1249        self.wants_keyboard_input || self.is_popup_open
1250    }
1251
1252    /// Returns `true` if any of the following is true:
1253    /// [`EguiWantsInput::wants_any_pointer_input`], [`EguiWantsInput::wants_any_keyboard_input`].
1254    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
1267/// Updates the [`EguiWantsInput`] resource.
1268pub 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
1288/// Returns `true` if any of the following is true:
1289/// [`EguiWantsInput::is_pointer_over_area`], [`EguiWantsInput::wants_pointer_input`], [`EguiWantsInput::is_using_pointer`], [`EguiWantsInput::is_context_menu_open`].
1290pub fn egui_wants_any_pointer_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1291    egui_wants_input_resource.wants_any_pointer_input()
1292}
1293
1294/// Returns `true` if any of the following is true:
1295/// [`EguiWantsInput::wants_keyboard_input`], [`EguiWantsInput::is_context_menu_open`].
1296pub fn egui_wants_any_keyboard_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1297    egui_wants_input_resource.wants_any_keyboard_input()
1298}
1299
1300/// Returns `true` if any of the following is true:
1301/// [`EguiWantsInput::wants_any_pointer_input`], [`EguiWantsInput::wants_any_keyboard_input`].
1302pub fn egui_wants_any_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1303    egui_wants_input_resource.wants_any_input()
1304}