bevy_egui/
input.rs

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/// Cached pointer position, used to populate [`egui::Event::PointerButton`] messages.
24#[derive(Component, Default)]
25pub struct EguiContextPointerPosition {
26    /// Pointer position.
27    pub position: egui::Pos2,
28}
29
30/// Stores an active touch id.
31#[derive(Component, Default)]
32pub struct EguiContextPointerTouchId {
33    /// Active touch id.
34    pub pointer_touch_id: Option<u64>,
35}
36
37/// Indicates whether [IME](https://en.wikipedia.org/wiki/Input_method) is enabled or disabled to avoid sending message duplicates.
38#[derive(Component, Default)]
39pub struct EguiContextImeState {
40    /// Indicates whether IME is enabled.
41    pub has_sent_ime_enabled: bool,
42    /// Indicates whether IME is currently allowed, i.e. if the virtual keyboard is shown.
43    pub is_ime_allowed: bool,
44    /// Corresponds to where an active egui text edit is located on the screen.
45    pub ime_rect: Option<egui::Rect>,
46}
47
48#[derive(Message)]
49/// Wraps Egui messages emitted by [`crate::EguiInputSet`] systems.
50pub struct EguiInputEvent {
51    /// Context to pass an message to.
52    pub context: Entity,
53    /// Wrapped event.
54    pub event: egui::Event,
55}
56
57#[derive(Message)]
58/// Wraps [`bevy::FileDragAndDrop`](bevy_window::FileDragAndDrop) messages emitted by [`crate::EguiInputSet`] systems.
59pub struct EguiFileDragAndDropMessage {
60    /// Context to pass an event to.
61    pub context: Entity,
62    /// Wrapped message.
63    pub message: FileDragAndDrop,
64}
65
66#[derive(Resource, Clone)]
67/// Insert this resource when a pointer hovers over a non-window (e.g. world-space) [`EguiContext`] entity.
68/// Also, make sure to update an [`EguiContextPointerPosition`] component of a hovered entity.
69/// Both updates should happen during [`crate::EguiInputSet::InitReading`].
70///
71/// To learn how `bevy_egui` uses this resource, see the [`FocusedNonWindowEguiContext`] documentation.
72pub struct HoveredNonWindowEguiContext(pub Entity);
73
74/// Stores an entity of a focused non-window context (to push keyboard messages to).
75///
76/// The resource won't exist if no context is focused, [`Option<Res<FocusedNonWindowEguiContext>>`] must be used to read from it.
77/// If the [`HoveredNonWindowEguiContext`] resource exists, the [`FocusedNonWindowEguiContext`]
78/// resource will get inserted on mouse button press or touch start message
79/// (and removed if no hovered non-window context exists respectively).
80///
81/// Atm, it's up to users to update [`HoveredNonWindowEguiContext`] and [`EguiContextPointerPosition`].
82/// We might be able to add proper `bevy_picking` support for world space UI once [`bevy_picking::backend::HitData`]
83/// starts exposing triangle index or UV.
84///
85/// Updating focused contexts happens during [`crate::EguiInputSet::FocusContext`],
86/// see [`write_pointer_button_messages_system`] and [`write_window_touch_messages_system`].
87#[derive(Resource, Clone)]
88pub struct FocusedNonWindowEguiContext(pub Entity);
89
90/// Stores "pressed" state of modifier keys.
91#[derive(Resource, Clone, Copy, Debug)]
92pub struct ModifierKeysState {
93    /// Indicates whether the [`Key::Shift`] key is pressed.
94    pub shift: bool,
95    /// Indicates whether the [`Key::Control`] key is pressed.
96    pub ctrl: bool,
97    /// Indicates whether the [`Key::Alt`] key is pressed.
98    pub alt: bool,
99    /// Indicates whether the [`Key::Super`] (or [`Key::Meta`]) key is pressed.
100    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    /// Converts the struct to [`egui::Modifiers`].
135    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    /// Returns `true` if modifiers shouldn't prmessage text input (we don't want to put characters on pressing Ctrl+A, etc).
146    pub fn text_input_is_allowed(&self) -> bool {
147        // Ctrl + Alt enables AltGr which is used to print special characters.
148        !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)]
160/// A bidirectional map between [`Window`] and [`EguiContext`] entities.
161/// Multiple contexts may belong to a single window.
162pub struct WindowToEguiContextMap {
163    /// Indexes contexts by windows.
164    pub window_to_contexts:
165        bevy_platform::collections::HashMap<Entity, bevy_platform::collections::HashSet<Entity>>,
166    /// Indexes windows by contexts.
167    pub context_to_window: bevy_platform::collections::HashMap<Entity, Entity>,
168}
169
170impl WindowToEguiContextMap {
171    /// Adds a context to the map on creation.
172    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                // The resource doesn't exist in the headless mode.
190                let message_loop_proxy = (*event_loop_proxy).clone();
191                egui_context
192                    .get_mut()
193                    .set_request_repaint_callback(move |repaint_info| {
194                        // TODO: find a lightweight async timer implementation that also works in WASM
195                        //  to support non-zero wake-ups as well.
196                        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    /// Removes a context from the map on removal.
206    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
227/// Iterates over pairs of `(Message, Entity)`, where the entity points to the context that the message is related to.
228pub 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)]
269/// A helper system param to iterate over pairs of messages and Egui contexts, see [`EguiContextsMessageIterator`].
270pub 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    /// Returns [`EguiContextsMessageIterator`] that iterates only over window messages (i.e. skips contexts that render to images, etc.),
279    /// expects a lambda that extracts a window id from an message.
280    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    /// Returns [`EguiContextsMessageIterator`] that iterates over window messages but might substitute contexts with a currently hovered non-window context (see [`HoveredNonWindowEguiContext`]), expects a lambda that extracts a window id from an message.
299    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    /// Returns [`EguiContextsMessageIterator`] that iterates over window messages but might substitute contexts with a currently focused non-window context (see [`FocusedNonWindowEguiContext`]), expects a lambda that extracts a window id from an message.
321    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
343/// Reads [`KeyboardInput`] messages to update the [`ModifierKeysState`] resource.
344pub 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 window focus is lost, clear all modifiers to avoid stuck keys.
350    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
377/// Reads [`MouseButtonInput`] messages and wraps them into [`EguiInputEvent`] (only for window contexts).
378pub 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
410/// Reads [`MouseButtonInput`] messages and wraps them into [`EguiInputEvent`], can redirect messages to [`HoveredNonWindowEguiContext`],
411/// inserts, updates or removes the [`FocusedNonWindowEguiContext`] resource based on a hovered context.
412pub 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 we are hovering over some UI in world space, we want to mark it as focused on mouse click.
466        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
478/// Reads [`CursorMoved`] messages and wraps them into [`EguiInputEvent`] for a [`HoveredNonWindowEguiContext`] context (if one exists).
479pub 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
515/// Reads [`MouseWheel`] messages and wraps them into [`EguiInputEvent`], can redirect messages to [`HoveredNonWindowEguiContext`].
516pub 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
554/// Reads [`KeyboardInput`] messages and wraps them into [`EguiInputEvent`], can redirect messages to [`FocusedNonWindowEguiContext`].
555pub 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        // "Logical OR physical key" is a fallback mechanism for keyboard layouts without Latin characters
604        // See: https://github.com/emilk/egui/blob/66c73b9cbfbd4d44489fc6f6a840d7d82bc34389/crates/egui-winit/src/lib.rs#L760
605        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        // We also check that it's a `ButtonState::Pressed` message, as we don't want to
622        // copy, cut or paste on the key release.
623        #[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
656/// Reads [`Ime`] messages and wraps them into [`EguiInputEvent`], can redirect messages to [`FocusedNonWindowEguiContext`].
657pub 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        // Aligned with the egui-winit implementation: https://github.com/emilk/egui/blob/0f2b427ff4c0a8c68f6622ec7d0afb7ba7e71bba/crates/egui-winit/src/lib.rs#L348
714        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
748/// Show the virtual keyboard when a text input is focused.
749/// Works by reading [`EguiOutput`] and calling `Window::set_ime_allowed` if the `ime` field is set.
750pub 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
808/// Reads [`FileDragAndDrop`] messages and wraps them into [`EguiFileDragAndDropMessage`], can redirect messages to [`HoveredNonWindowEguiContext`].
809pub 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
859/// Reads [`TouchInput`] messages and wraps them into [`EguiInputEvent`].
860pub 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
931/// Reads [`TouchInput`] messages and wraps them into [`EguiInputEvent`] for a [`HoveredNonWindowEguiContext`] context (if one exists).
932pub 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    // Emit the touch message.
996    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 we're not yet translating a touch, or we're translating this very
1021    // touch, …
1022    if context_pointer_touch_id.pointer_touch_id.is_none()
1023        || context_pointer_touch_id.pointer_touch_id.unwrap() == message.id
1024    {
1025        // … emit PointerButton resp. PointerMoved messages to emulate mouse.
1026        match message.phase {
1027            bevy_input::touch::TouchPhase::Started => {
1028                context_pointer_touch_id.pointer_touch_id = Some(message.id);
1029                // First move the pointer to the right location.
1030                egui_input_message_writer.write(EguiInputEvent {
1031                    context,
1032                    event: egui::Event::PointerMoved(pointer_position),
1033                });
1034                // Then do mouse button input.
1035                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/// Reads both [`EguiFileDragAndDropMessage`] and [`EguiInputEvent`] messages and feeds them to Egui.
1087#[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
1171/// Clears Bevy input message buffers and resets [`ButtonInput`] resources if Egui
1172/// is using pointer or keyboard (see the [`write_egui_wants_input_system`] run condition).
1173///
1174/// This system isn't run by default, set [`EguiGlobalSettings::enable_absorb_bevy_input_system`]
1175/// to `true` to enable it.
1176///
1177/// ## Considerations
1178///
1179/// Enabling this system makes an assumption that `bevy_egui` takes priority in input handling
1180/// over other plugins and systems. This should work ok as long as there's no other system
1181/// clearing messages the same way that might be in conflict with `bevy_egui`, and there's
1182/// no other system that needs a non-interrupted flow of messages.
1183///
1184/// ## Alternative
1185///
1186/// 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
1187/// that need to be disabled while Egui is using input (see the [`egui_wants_any_pointer_input`], [`egui_wants_any_keyboard_input`] run conditions).
1188pub 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    // TODO: the list of messages is definitely not comprehensive, but it should at least cover
1210    //  the most popular use-cases. We can add more on request.
1211    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/// Stores whether there's an Egui context using pointer or keyboard.
1227#[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    /// Is the pointer (mouse/touch) over any egui area?
1238    pub fn is_pointer_over_area(&self) -> bool {
1239        self.is_pointer_over_area
1240    }
1241
1242    /// True if egui is currently interested in the pointer (mouse or touch).
1243    ///
1244    /// Could be the pointer is hovering over a [`egui::Window`] or the user is dragging a widget.
1245    /// If `false`, the pointer is outside of any egui area and so
1246    /// you may be interested in what it is doing (e.g. controlling your game).
1247    /// Returns `false` if a drag started outside of egui and then moved over an egui area.
1248    pub fn wants_pointer_input(&self) -> bool {
1249        self.wants_pointer_input
1250    }
1251
1252    /// Is egui currently using the pointer position (e.g. dragging a slider)?
1253    ///
1254    /// NOTE: this will return `false` if the pointer is just hovering over an egui area.
1255    pub fn is_using_pointer(&self) -> bool {
1256        self.is_using_pointer
1257    }
1258
1259    /// If `true`, egui is currently listening on text input (e.g. typing text in a [`egui::TextEdit`]).
1260    pub fn wants_keyboard_input(&self) -> bool {
1261        self.wants_keyboard_input
1262    }
1263
1264    /// Is an egui context menu open?
1265    #[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    /// Is an egui context menu open?
1271    pub fn is_popup_open(&self) -> bool {
1272        self.is_popup_open
1273    }
1274
1275    /// Returns `true` if any of the following is true:
1276    /// [`EguiWantsInput::is_pointer_over_area`], [`EguiWantsInput::wants_pointer_input`], [`EguiWantsInput::is_using_pointer`], [`EguiWantsInput::is_context_menu_open`].
1277    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    /// Returns `true` if any of the following is true:
1285    /// [`EguiWantsInput::wants_keyboard_input`], [`EguiWantsInput::is_context_menu_open`].
1286    pub fn wants_any_keyboard_input(&self) -> bool {
1287        self.wants_keyboard_input || self.is_popup_open
1288    }
1289
1290    /// Returns `true` if any of the following is true:
1291    /// [`EguiWantsInput::wants_any_pointer_input`], [`EguiWantsInput::wants_any_keyboard_input`].
1292    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
1305/// Updates the [`EguiWantsInput`] resource.
1306pub 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
1326/// Returns `true` if any of the following is true:
1327/// [`EguiWantsInput::is_pointer_over_area`], [`EguiWantsInput::wants_pointer_input`], [`EguiWantsInput::is_using_pointer`], [`EguiWantsInput::is_context_menu_open`].
1328pub fn egui_wants_any_pointer_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1329    egui_wants_input_resource.wants_any_pointer_input()
1330}
1331
1332/// Returns `true` if any of the following is true:
1333/// [`EguiWantsInput::wants_keyboard_input`], [`EguiWantsInput::is_context_menu_open`].
1334pub fn egui_wants_any_keyboard_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1335    egui_wants_input_resource.wants_any_keyboard_input()
1336}
1337
1338/// Returns `true` if any of the following is true:
1339/// [`EguiWantsInput::wants_any_pointer_input`], [`EguiWantsInput::wants_any_keyboard_input`].
1340pub fn egui_wants_any_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1341    egui_wants_input_resource.wants_any_input()
1342}