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::prelude::*;
8use bevy_input::{
9    keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput},
10    mouse::{MouseButton, MouseButtonInput, MouseScrollUnit, MouseWheel},
11    touch::TouchInput,
12    ButtonInput, ButtonState,
13};
14use bevy_log as log;
15use bevy_time::{Real, Time};
16use bevy_window::{CursorMoved, FileDragAndDrop, Ime, Window};
17use egui::Modifiers;
18
19/// 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}
39
40#[derive(Event)]
41/// Wraps Egui events emitted by [`crate::EguiInputSet`] systems.
42pub struct EguiInputEvent {
43    /// Context to pass an event to.
44    pub context: Entity,
45    /// Wrapped event.
46    pub event: egui::Event,
47}
48
49#[derive(Event)]
50/// Wraps [`bevy::FileDragAndDrop`](bevy_window::FileDragAndDrop) events emitted by [`crate::EguiInputSet`] systems.
51pub struct EguiFileDragAndDropEvent {
52    /// Context to pass an event to.
53    pub context: Entity,
54    /// Wrapped event.
55    pub event: FileDragAndDrop,
56}
57
58#[derive(Resource)]
59/// Insert this resource when a pointer hovers over a non-window (e.g. world-space) [`EguiContext`] entity.
60/// Also, make sure to update an [`EguiContextPointerPosition`] component of a hovered entity.
61/// Both updates should happen during [`crate::EguiInputSet::InitReading`].
62///
63/// To learn how `bevy_egui` uses this resource, see the [`FocusedNonWindowEguiContext`] documentation.
64pub struct HoveredNonWindowEguiContext(pub Entity);
65
66/// Stores an entity of a focused non-window context (to push keyboard events to).
67///
68/// The resource won't exist if no context is focused, [`Option<Res<HoveredNonWindowEguiContext>>`] must be used to read from it.
69/// If the [`HoveredNonWindowEguiContext`] resource exists, the [`FocusedNonWindowEguiContext`]
70/// resource will get inserted on mouse button press or touch start event
71/// (and removed if no hovered non-window context exists respectively).
72///
73/// Atm, it's up to users to update [`HoveredNonWindowEguiContext`] and [`EguiContextPointerPosition`].
74/// We might be able to add proper `bevy_picking` support for world space UI once [`bevy_picking::backend::HitData`]
75/// starts exposing triangle index or UV.
76///
77/// Updating focused contexts happens during [`crate::EguiInputSet::FocusContext`],
78/// see [`write_pointer_button_events_system`] and [`write_window_touch_events_system`].
79#[derive(Resource)]
80pub struct FocusedNonWindowEguiContext(pub Entity);
81
82/// Stores "pressed" state of modifier keys.
83#[derive(Resource, Clone, Copy, Debug)]
84pub struct ModifierKeysState {
85    /// Indicates whether the [`Key::Shift`] key is pressed.
86    pub shift: bool,
87    /// Indicates whether the [`Key::Control`] key is pressed.
88    pub ctrl: bool,
89    /// Indicates whether the [`Key::Alt`] key is pressed.
90    pub alt: bool,
91    /// Indicates whether the [`Key::Super`] (or [`Key::Meta`]) key is pressed.
92    pub win: bool,
93    is_macos: bool,
94}
95
96impl Default for ModifierKeysState {
97    fn default() -> Self {
98        let mut state = Self {
99            shift: false,
100            ctrl: false,
101            alt: false,
102            win: false,
103            is_macos: false,
104        };
105
106        #[cfg(not(target_arch = "wasm32"))]
107        {
108            state.is_macos = cfg!(target_os = "macos");
109        }
110
111        #[cfg(target_arch = "wasm32")]
112        if let Some(window) = web_sys::window() {
113            let nav = window.navigator();
114            if let Ok(user_agent) = nav.user_agent() {
115                if user_agent.to_ascii_lowercase().contains("mac") {
116                    state.is_macos = true;
117                }
118            }
119        }
120
121        state
122    }
123}
124
125impl ModifierKeysState {
126    /// Converts the struct to [`egui::Modifiers`].
127    pub fn to_egui_modifiers(&self) -> egui::Modifiers {
128        egui::Modifiers {
129            alt: self.alt,
130            ctrl: self.ctrl,
131            shift: self.shift,
132            mac_cmd: if self.is_macos { self.win } else { false },
133            command: if self.is_macos { self.win } else { self.ctrl },
134        }
135    }
136
137    /// Returns `true` if modifiers shouldn't prevent text input (we don't want to put characters on pressing Ctrl+A, etc).
138    pub fn text_input_is_allowed(&self) -> bool {
139        // Ctrl + Alt enables AltGr which is used to print special characters.
140        !self.win && !self.ctrl || !self.is_macos && self.ctrl && self.alt
141    }
142
143    fn reset(&mut self) {
144        self.shift = false;
145        self.ctrl = false;
146        self.alt = false;
147        self.win = false;
148    }
149}
150
151/// Reads [`KeyboardInput`] events to update the [`ModifierKeysState`] resource.
152pub fn write_modifiers_keys_state_system(
153    mut ev_keyboard_input: EventReader<KeyboardInput>,
154    mut ev_focus: EventReader<KeyboardFocusLost>,
155    mut modifier_keys_state: ResMut<ModifierKeysState>,
156) {
157    // If window focus is lost, clear all modifiers to avoid stuck keys.
158    if !ev_focus.is_empty() {
159        ev_focus.clear();
160        modifier_keys_state.reset();
161    }
162
163    for event in ev_keyboard_input.read() {
164        let KeyboardInput {
165            logical_key, state, ..
166        } = event;
167        match logical_key {
168            Key::Shift => {
169                modifier_keys_state.shift = state.is_pressed();
170            }
171            Key::Control => {
172                modifier_keys_state.ctrl = state.is_pressed();
173            }
174            Key::Alt => {
175                modifier_keys_state.alt = state.is_pressed();
176            }
177            Key::Super | Key::Meta => {
178                modifier_keys_state.win = state.is_pressed();
179            }
180            _ => {}
181        };
182    }
183}
184
185/// Reads [`MouseButtonInput`] events and wraps them into [`EguiInputEvent`] (only for window contexts).
186pub fn write_window_pointer_moved_events_system(
187    mut cursor_moved_reader: EventReader<CursorMoved>,
188    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
189    mut egui_contexts: Query<
190        (&EguiContextSettings, &mut EguiContextPointerPosition),
191        (With<EguiContext>, With<Window>),
192    >,
193) {
194    for event in cursor_moved_reader.read() {
195        let Some((context_settings, mut context_pointer_position)) =
196            egui_contexts.get_some_mut(event.window)
197        else {
198            continue;
199        };
200
201        if !context_settings
202            .input_system_settings
203            .run_write_window_pointer_moved_events_system
204        {
205            continue;
206        }
207
208        let scale_factor = context_settings.scale_factor;
209        let pointer_position = vec2_into_egui_pos2(event.position / scale_factor);
210        context_pointer_position.position = pointer_position;
211        egui_input_event_writer.write(EguiInputEvent {
212            context: event.window,
213            event: egui::Event::PointerMoved(pointer_position),
214        });
215    }
216}
217
218/// Reads [`MouseButtonInput`] events and wraps them into [`EguiInputEvent`], can redirect events to [`HoveredNonWindowEguiContext`],
219/// inserts, updates or removes the [`FocusedNonWindowEguiContext`] resource based on a hovered context.
220pub fn write_pointer_button_events_system(
221    egui_global_settings: Res<EguiGlobalSettings>,
222    mut commands: Commands,
223    hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
224    modifier_keys_state: Res<ModifierKeysState>,
225    mut mouse_button_input_reader: EventReader<MouseButtonInput>,
226    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
227    egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
228) {
229    let modifiers = modifier_keys_state.to_egui_modifiers();
230    for event in mouse_button_input_reader.read() {
231        let hovered_context = hovered_non_window_egui_context
232            .as_deref()
233            .map_or(event.window, |hovered| hovered.0);
234
235        let Some((context_settings, context_pointer_position)) =
236            egui_contexts.get_some(hovered_context)
237        else {
238            continue;
239        };
240
241        if !context_settings
242            .input_system_settings
243            .run_write_pointer_button_events_system
244        {
245            continue;
246        }
247
248        let button = match event.button {
249            MouseButton::Left => Some(egui::PointerButton::Primary),
250            MouseButton::Right => Some(egui::PointerButton::Secondary),
251            MouseButton::Middle => Some(egui::PointerButton::Middle),
252            MouseButton::Back => Some(egui::PointerButton::Extra1),
253            MouseButton::Forward => Some(egui::PointerButton::Extra2),
254            _ => None,
255        };
256        let Some(button) = button else {
257            continue;
258        };
259        let pressed = match event.state {
260            ButtonState::Pressed => true,
261            ButtonState::Released => false,
262        };
263        egui_input_event_writer.write(EguiInputEvent {
264            context: hovered_context,
265            event: egui::Event::PointerButton {
266                pos: context_pointer_position.position,
267                button,
268                pressed,
269                modifiers,
270            },
271        });
272
273        // If we are hovering over some UI in world space, we want to mark it as focused on mouse click.
274        if egui_global_settings.enable_focused_non_window_context_updates && pressed {
275            if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
276                commands.insert_resource(FocusedNonWindowEguiContext(
277                    hovered_non_window_egui_context.0,
278                ));
279            } else {
280                commands.remove_resource::<FocusedNonWindowEguiContext>();
281            }
282        }
283    }
284}
285
286/// Reads [`CursorMoved`] events and wraps them into [`EguiInputEvent`] for a [`HoveredNonWindowEguiContext`] context (if one exists).
287pub fn write_non_window_pointer_moved_events_system(
288    hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
289    mut cursor_moved_reader: EventReader<CursorMoved>,
290    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
291    egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
292) {
293    if cursor_moved_reader.is_empty() {
294        return;
295    }
296
297    cursor_moved_reader.clear();
298    let Some(HoveredNonWindowEguiContext(hovered_non_window_egui_context)) =
299        hovered_non_window_egui_context.as_deref()
300    else {
301        return;
302    };
303
304    let Some((context_settings, context_pointer_position)) =
305        egui_contexts.get_some(*hovered_non_window_egui_context)
306    else {
307        return;
308    };
309
310    if !context_settings
311        .input_system_settings
312        .run_write_non_window_pointer_moved_events_system
313    {
314        return;
315    }
316
317    egui_input_event_writer.write(EguiInputEvent {
318        context: *hovered_non_window_egui_context,
319        event: egui::Event::PointerMoved(context_pointer_position.position),
320    });
321}
322
323/// Reads [`MouseWheel`] events and wraps them into [`EguiInputEvent`], can redirect events to [`HoveredNonWindowEguiContext`].
324pub fn write_mouse_wheel_events_system(
325    modifier_keys_state: Res<ModifierKeysState>,
326    hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
327    mut mouse_wheel_reader: EventReader<MouseWheel>,
328    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
329    egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
330) {
331    let modifiers = modifier_keys_state.to_egui_modifiers();
332    for event in mouse_wheel_reader.read() {
333        let delta = egui::vec2(event.x, event.y);
334        let unit = match event.unit {
335            MouseScrollUnit::Line => egui::MouseWheelUnit::Line,
336            MouseScrollUnit::Pixel => egui::MouseWheelUnit::Point,
337        };
338
339        let context = hovered_non_window_egui_context
340            .as_deref()
341            .map_or(event.window, |hovered| hovered.0);
342
343        let Some(context_settings) = egui_contexts.get_some(context) else {
344            continue;
345        };
346
347        if !context_settings
348            .input_system_settings
349            .run_write_mouse_wheel_events_system
350        {
351            continue;
352        }
353
354        egui_input_event_writer.write(EguiInputEvent {
355            context,
356            event: egui::Event::MouseWheel {
357                unit,
358                delta,
359                modifiers,
360            },
361        });
362    }
363}
364
365/// Reads [`KeyboardInput`] events and wraps them into [`EguiInputEvent`], can redirect events to [`FocusedNonWindowEguiContext`].
366pub fn write_keyboard_input_events_system(
367    modifier_keys_state: Res<ModifierKeysState>,
368    focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
369    #[cfg(all(
370        feature = "manage_clipboard",
371        not(target_os = "android"),
372        not(target_arch = "wasm32")
373    ))]
374    mut egui_clipboard: ResMut<crate::EguiClipboard>,
375    mut keyboard_input_reader: EventReader<KeyboardInput>,
376    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
377    egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
378) {
379    let modifiers = modifier_keys_state.to_egui_modifiers();
380    for event in keyboard_input_reader.read() {
381        let context = focused_non_window_egui_context
382            .as_deref()
383            .map_or(event.window, |context| context.0);
384
385        let Some(context_settings) = egui_contexts.get_some(context) else {
386            continue;
387        };
388
389        if !context_settings
390            .input_system_settings
391            .run_write_keyboard_input_events_system
392        {
393            continue;
394        }
395
396        if modifier_keys_state.text_input_is_allowed() && event.state.is_pressed() {
397            match &event.logical_key {
398                Key::Character(char) if char.matches(char::is_control).count() == 0 => {
399                    egui_input_event_writer.write(EguiInputEvent {
400                        context,
401                        event: egui::Event::Text(char.to_string()),
402                    });
403                }
404                Key::Space => {
405                    egui_input_event_writer.write(EguiInputEvent {
406                        context,
407                        event: egui::Event::Text(" ".to_string()),
408                    });
409                }
410                _ => (),
411            }
412        }
413
414        let key = crate::helpers::bevy_to_egui_key(&event.logical_key);
415        let physical_key = crate::helpers::bevy_to_egui_physical_key(&event.key_code);
416
417        // "Logical OR physical key" is a fallback mechanism for keyboard layouts without Latin characters
418        // See: https://github.com/emilk/egui/blob/66c73b9cbfbd4d44489fc6f6a840d7d82bc34389/crates/egui-winit/src/lib.rs#L760
419        let (Some(key), physical_key) = (key.or(physical_key), physical_key) else {
420            continue;
421        };
422
423        let egui_event = egui::Event::Key {
424            key,
425            pressed: event.state.is_pressed(),
426            repeat: false,
427            modifiers,
428            physical_key,
429        };
430        egui_input_event_writer.write(EguiInputEvent {
431            context,
432            event: egui_event,
433        });
434
435        // We also check that it's a `ButtonState::Pressed` event, as we don't want to
436        // copy, cut or paste on the key release.
437        #[cfg(all(
438            feature = "manage_clipboard",
439            not(target_os = "android"),
440            not(target_arch = "wasm32")
441        ))]
442        if modifiers.command && event.state.is_pressed() {
443            match key {
444                egui::Key::C => {
445                    egui_input_event_writer.write(EguiInputEvent {
446                        context,
447                        event: egui::Event::Copy,
448                    });
449                }
450                egui::Key::X => {
451                    egui_input_event_writer.write(EguiInputEvent {
452                        context,
453                        event: egui::Event::Cut,
454                    });
455                }
456                egui::Key::V => {
457                    if let Some(contents) = egui_clipboard.get_text() {
458                        egui_input_event_writer.write(EguiInputEvent {
459                            context,
460                            event: egui::Event::Text(contents),
461                        });
462                    }
463                }
464                _ => {}
465            }
466        }
467    }
468}
469
470/// Reads [`Ime`] events and wraps them into [`EguiInputEvent`], can redirect events to [`FocusedNonWindowEguiContext`].
471pub fn write_ime_events_system(
472    focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
473    mut ime_reader: EventReader<Ime>,
474    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
475    mut egui_contexts: Query<
476        (
477            Entity,
478            &EguiContextSettings,
479            &mut EguiContextImeState,
480            &EguiOutput,
481        ),
482        With<EguiContext>,
483    >,
484) {
485    for event in ime_reader.read() {
486        let window = match &event {
487            Ime::Preedit { window, .. }
488            | Ime::Commit { window, .. }
489            | Ime::Disabled { window }
490            | Ime::Enabled { window } => *window,
491        };
492        let context = focused_non_window_egui_context
493            .as_deref()
494            .map_or(window, |context| context.0);
495
496        let Some((_entity, context_settings, mut ime_state, _egui_output)) =
497            egui_contexts.get_some_mut(context)
498        else {
499            continue;
500        };
501
502        if !context_settings
503            .input_system_settings
504            .run_write_ime_events_system
505        {
506            continue;
507        }
508
509        let ime_event_enable =
510            |ime_state: &mut EguiContextImeState,
511             egui_input_event_writer: &mut EventWriter<EguiInputEvent>| {
512                if !ime_state.has_sent_ime_enabled {
513                    egui_input_event_writer.write(EguiInputEvent {
514                        context,
515                        event: egui::Event::Ime(egui::ImeEvent::Enabled),
516                    });
517                    ime_state.has_sent_ime_enabled = true;
518                }
519            };
520
521        let ime_event_disable =
522            |ime_state: &mut EguiContextImeState,
523             egui_input_event_writer: &mut EventWriter<EguiInputEvent>| {
524                if !ime_state.has_sent_ime_enabled {
525                    egui_input_event_writer.write(EguiInputEvent {
526                        context,
527                        event: egui::Event::Ime(egui::ImeEvent::Disabled),
528                    });
529                    ime_state.has_sent_ime_enabled = false;
530                }
531            };
532
533        // Aligned with the egui-winit implementation: https://github.com/emilk/egui/blob/0f2b427ff4c0a8c68f6622ec7d0afb7ba7e71bba/crates/egui-winit/src/lib.rs#L348
534        match event {
535            Ime::Enabled { window: _ } => {
536                ime_event_enable(&mut ime_state, &mut egui_input_event_writer);
537            }
538            Ime::Preedit {
539                value,
540                window: _,
541                cursor: _,
542            } => {
543                ime_event_enable(&mut ime_state, &mut egui_input_event_writer);
544                egui_input_event_writer.write(EguiInputEvent {
545                    context,
546                    event: egui::Event::Ime(egui::ImeEvent::Preedit(value.clone())),
547                });
548            }
549            Ime::Commit { value, window: _ } => {
550                egui_input_event_writer.write(EguiInputEvent {
551                    context,
552                    event: egui::Event::Ime(egui::ImeEvent::Commit(value.clone())),
553                });
554                ime_event_disable(&mut ime_state, &mut egui_input_event_writer);
555            }
556            Ime::Disabled { window: _ } => {
557                ime_event_disable(&mut ime_state, &mut egui_input_event_writer);
558            }
559        }
560    }
561}
562
563/// Reads [`FileDragAndDrop`] events and wraps them into [`EguiFileDragAndDropEvent`], can redirect events to [`FocusedNonWindowEguiContext`].
564pub fn write_file_dnd_events_system(
565    focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
566    mut dnd_reader: EventReader<FileDragAndDrop>,
567    mut egui_file_dnd_event_writer: EventWriter<EguiFileDragAndDropEvent>,
568    egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
569) {
570    for event in dnd_reader.read() {
571        let window = match &event {
572            FileDragAndDrop::DroppedFile { window, .. }
573            | FileDragAndDrop::HoveredFile { window, .. }
574            | FileDragAndDrop::HoveredFileCanceled { window } => *window,
575        };
576        let context = focused_non_window_egui_context
577            .as_deref()
578            .map_or(window, |context| context.0);
579
580        let Some(context_settings) = egui_contexts.get_some(context) else {
581            continue;
582        };
583
584        if !context_settings
585            .input_system_settings
586            .run_write_file_dnd_events_system
587        {
588            continue;
589        }
590
591        match event {
592            FileDragAndDrop::DroppedFile { window, path_buf } => {
593                egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
594                    context,
595                    event: FileDragAndDrop::DroppedFile {
596                        window: *window,
597                        path_buf: path_buf.clone(),
598                    },
599                });
600            }
601            FileDragAndDrop::HoveredFile { window, path_buf } => {
602                egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
603                    context,
604                    event: FileDragAndDrop::HoveredFile {
605                        window: *window,
606                        path_buf: path_buf.clone(),
607                    },
608                });
609            }
610            FileDragAndDrop::HoveredFileCanceled { window } => {
611                egui_file_dnd_event_writer.write(EguiFileDragAndDropEvent {
612                    context,
613                    event: FileDragAndDrop::HoveredFileCanceled { window: *window },
614                });
615            }
616        }
617    }
618}
619
620/// Reads [`TouchInput`] events and wraps them into [`EguiInputEvent`].
621pub fn write_window_touch_events_system(
622    mut commands: Commands,
623    egui_global_settings: Res<EguiGlobalSettings>,
624    hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
625    modifier_keys_state: Res<ModifierKeysState>,
626    mut touch_input_reader: EventReader<TouchInput>,
627    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
628    mut egui_contexts: Query<
629        (
630            &EguiContextSettings,
631            &mut EguiContextPointerPosition,
632            &mut EguiContextPointerTouchId,
633            &EguiOutput,
634        ),
635        (With<EguiContext>, With<Window>),
636    >,
637) {
638    let modifiers = modifier_keys_state.to_egui_modifiers();
639    for event in touch_input_reader.read() {
640        let Some((
641            context_settings,
642            mut context_pointer_position,
643            mut context_pointer_touch_id,
644            output,
645        )) = egui_contexts.get_some_mut(event.window)
646        else {
647            continue;
648        };
649
650        if egui_global_settings.enable_focused_non_window_context_updates {
651            if let bevy_input::touch::TouchPhase::Started = event.phase {
652                if let Some(hovered_non_window_egui_context) =
653                    hovered_non_window_egui_context.as_deref()
654                {
655                    if let bevy_input::touch::TouchPhase::Started = event.phase {
656                        commands.insert_resource(FocusedNonWindowEguiContext(
657                            hovered_non_window_egui_context.0,
658                        ));
659                    }
660
661                    continue;
662                }
663
664                commands.remove_resource::<FocusedNonWindowEguiContext>();
665            }
666        }
667
668        if !context_settings
669            .input_system_settings
670            .run_write_window_touch_events_system
671        {
672            continue;
673        }
674
675        let scale_factor = context_settings.scale_factor;
676        let touch_position = vec2_into_egui_pos2(event.position / scale_factor);
677        context_pointer_position.position = touch_position;
678        write_touch_event(
679            &mut egui_input_event_writer,
680            event,
681            event.window,
682            output,
683            touch_position,
684            modifiers,
685            &mut context_pointer_touch_id,
686        );
687    }
688}
689
690/// Reads [`TouchInput`] events and wraps them into [`EguiInputEvent`] for a [`HoveredNonWindowEguiContext`] context (if one exists).
691pub fn write_non_window_touch_events_system(
692    focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
693    mut touch_input_reader: EventReader<TouchInput>,
694    mut egui_input_event_writer: EventWriter<EguiInputEvent>,
695    modifier_keys_state: Res<ModifierKeysState>,
696    mut egui_contexts: Query<
697        (
698            &EguiContextSettings,
699            &EguiContextPointerPosition,
700            &mut EguiContextPointerTouchId,
701            &EguiOutput,
702        ),
703        With<EguiContext>,
704    >,
705) {
706    let modifiers = modifier_keys_state.to_egui_modifiers();
707    for event in touch_input_reader.read() {
708        let Some(&FocusedNonWindowEguiContext(focused_non_window_egui_context)) =
709            focused_non_window_egui_context.as_deref()
710        else {
711            continue;
712        };
713
714        let Some((
715            context_settings,
716            context_pointer_position,
717            mut context_pointer_touch_id,
718            output,
719        )) = egui_contexts.get_some_mut(focused_non_window_egui_context)
720        else {
721            continue;
722        };
723
724        if !context_settings
725            .input_system_settings
726            .run_write_non_window_touch_events_system
727        {
728            continue;
729        }
730
731        write_touch_event(
732            &mut egui_input_event_writer,
733            event,
734            focused_non_window_egui_context,
735            output,
736            context_pointer_position.position,
737            modifiers,
738            &mut context_pointer_touch_id,
739        );
740    }
741}
742
743fn write_touch_event(
744    egui_input_event_writer: &mut EventWriter<EguiInputEvent>,
745    event: &TouchInput,
746    context: Entity,
747    _output: &EguiOutput,
748    pointer_position: egui::Pos2,
749    modifiers: Modifiers,
750    context_pointer_touch_id: &mut EguiContextPointerTouchId,
751) {
752    let touch_id = egui::TouchId::from(event.id);
753
754    // Emit touch event
755    egui_input_event_writer.write(EguiInputEvent {
756        context,
757        event: egui::Event::Touch {
758            device_id: egui::TouchDeviceId(event.window.to_bits()),
759            id: touch_id,
760            phase: match event.phase {
761                bevy_input::touch::TouchPhase::Started => egui::TouchPhase::Start,
762                bevy_input::touch::TouchPhase::Moved => egui::TouchPhase::Move,
763                bevy_input::touch::TouchPhase::Ended => egui::TouchPhase::End,
764                bevy_input::touch::TouchPhase::Canceled => egui::TouchPhase::Cancel,
765            },
766            pos: pointer_position,
767            force: match event.force {
768                Some(bevy_input::touch::ForceTouch::Normalized(force)) => Some(force as f32),
769                Some(bevy_input::touch::ForceTouch::Calibrated {
770                    force,
771                    max_possible_force,
772                    ..
773                }) => Some((force / max_possible_force) as f32),
774                None => None,
775            },
776        },
777    });
778
779    // If we're not yet translating a touch, or we're translating this very
780    // touch, …
781    if context_pointer_touch_id.pointer_touch_id.is_none()
782        || context_pointer_touch_id.pointer_touch_id.unwrap() == event.id
783    {
784        // … emit PointerButton resp. PointerMoved events to emulate mouse.
785        match event.phase {
786            bevy_input::touch::TouchPhase::Started => {
787                context_pointer_touch_id.pointer_touch_id = Some(event.id);
788                // First move the pointer to the right location.
789                egui_input_event_writer.write(EguiInputEvent {
790                    context,
791                    event: egui::Event::PointerMoved(pointer_position),
792                });
793                // Then do mouse button input.
794                egui_input_event_writer.write(EguiInputEvent {
795                    context,
796                    event: egui::Event::PointerButton {
797                        pos: pointer_position,
798                        button: egui::PointerButton::Primary,
799                        pressed: true,
800                        modifiers,
801                    },
802                });
803            }
804            bevy_input::touch::TouchPhase::Moved => {
805                egui_input_event_writer.write(EguiInputEvent {
806                    context,
807                    event: egui::Event::PointerMoved(pointer_position),
808                });
809            }
810            bevy_input::touch::TouchPhase::Ended => {
811                context_pointer_touch_id.pointer_touch_id = None;
812                egui_input_event_writer.write(EguiInputEvent {
813                    context,
814                    event: egui::Event::PointerButton {
815                        pos: pointer_position,
816                        button: egui::PointerButton::Primary,
817                        pressed: false,
818                        modifiers,
819                    },
820                });
821                egui_input_event_writer.write(EguiInputEvent {
822                    context,
823                    event: egui::Event::PointerGone,
824                });
825
826                #[cfg(target_arch = "wasm32")]
827                if !is_mobile_safari() {
828                    update_text_agent(
829                        _output.platform_output.ime.is_some()
830                            || _output.platform_output.mutable_text_under_cursor,
831                    );
832                }
833            }
834            bevy_input::touch::TouchPhase::Canceled => {
835                context_pointer_touch_id.pointer_touch_id = None;
836                egui_input_event_writer.write(EguiInputEvent {
837                    context,
838                    event: egui::Event::PointerGone,
839                });
840            }
841        }
842    }
843}
844
845/// Reads both [`EguiFileDragAndDropEvent`] and [`EguiInputEvent`] events and feeds them to Egui.
846pub fn write_egui_input_system(
847    focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
848    modifier_keys_state: Res<ModifierKeysState>,
849    mut egui_input_event_reader: EventReader<EguiInputEvent>,
850    mut egui_file_dnd_event_reader: EventReader<EguiFileDragAndDropEvent>,
851    mut egui_contexts: Query<(Entity, &mut EguiInput, Option<&Window>)>,
852    time: Res<Time<Real>>,
853) {
854    for EguiInputEvent { context, event } in egui_input_event_reader.read() {
855        #[cfg(feature = "log_input_events")]
856        log::warn!("{context:?}: {event:?}");
857
858        let (_, mut egui_input, _) = match egui_contexts.get_mut(*context) {
859            Ok(egui_input) => egui_input,
860            Err(err) => {
861                log::error!(
862                    "Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
863                );
864                continue;
865            }
866        };
867
868        egui_input.events.push(event.clone());
869    }
870
871    for EguiFileDragAndDropEvent { context, event } in egui_file_dnd_event_reader.read() {
872        #[cfg(feature = "log_file_dnd_events")]
873        log::warn!("{context:?}: {event:?}");
874
875        let (_, mut egui_input, _) = match egui_contexts.get_mut(*context) {
876            Ok(egui_input) => egui_input,
877            Err(err) => {
878                log::error!(
879                    "Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
880                );
881                continue;
882            }
883        };
884
885        match event {
886            FileDragAndDrop::DroppedFile {
887                window: _,
888                path_buf,
889            } => {
890                egui_input.hovered_files.clear();
891                egui_input.dropped_files.push(egui::DroppedFile {
892                    path: Some(path_buf.clone()),
893                    ..Default::default()
894                });
895            }
896            FileDragAndDrop::HoveredFile {
897                window: _,
898                path_buf,
899            } => {
900                egui_input.hovered_files.push(egui::HoveredFile {
901                    path: Some(path_buf.clone()),
902                    ..Default::default()
903                });
904            }
905            FileDragAndDrop::HoveredFileCanceled { window: _ } => {
906                egui_input.hovered_files.clear();
907            }
908        }
909    }
910
911    for (entity, mut egui_input, window) in egui_contexts.iter_mut() {
912        egui_input.focused = focused_non_window_egui_context.as_deref().map_or_else(
913            || window.is_some_and(|window| window.focused),
914            |context| context.0 == entity,
915        );
916        egui_input.modifiers = modifier_keys_state.to_egui_modifiers();
917        egui_input.time = Some(time.elapsed_secs_f64());
918    }
919}
920
921/// Clears Bevy input event buffers and resets [`ButtonInput`] resources if Egui
922/// is using pointer or keyboard (see the [`write_egui_wants_input_system`] run condition).
923///
924/// This system isn't run by default, set [`EguiGlobalSettings::enable_absorb_bevy_input_system`]
925/// to `true` to enable it.
926///
927/// ## Considerations
928///
929/// Enabling this system makes an assumption that `bevy_egui` takes priority in input handling
930/// over other plugins and systems. This should work ok as long as there's no other system
931/// clearing events the same way that might be in conflict with `bevy_egui`, and there's
932/// no other system that needs a non-interrupted flow of events.
933///
934/// ## Alternative
935///
936/// 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
937/// that need to be disabled while Egui is using input (see the [`egui_wants_any_pointer_input`], [`egui_wants_any_keyboard_input`] run conditions).
938pub fn absorb_bevy_input_system(
939    egui_wants_input: Res<EguiWantsInput>,
940    mut mouse_input: ResMut<ButtonInput<MouseButton>>,
941    mut keyboard_input: ResMut<ButtonInput<KeyCode>>,
942    mut keyboard_input_events: ResMut<Events<KeyboardInput>>,
943    mut mouse_wheel_events: ResMut<Events<MouseWheel>>,
944    mut mouse_button_input_events: ResMut<Events<MouseButtonInput>>,
945) {
946    let modifiers = [
947        KeyCode::SuperLeft,
948        KeyCode::SuperRight,
949        KeyCode::ControlLeft,
950        KeyCode::ControlRight,
951        KeyCode::AltLeft,
952        KeyCode::AltRight,
953        KeyCode::ShiftLeft,
954        KeyCode::ShiftRight,
955    ];
956
957    let pressed = modifiers.map(|key| keyboard_input.pressed(key).then_some(key));
958
959    // TODO: the list of events is definitely not comprehensive, but it should at least cover
960    //  the most popular use-cases. We can add more on request.
961    if egui_wants_input.wants_any_keyboard_input() {
962        keyboard_input.reset_all();
963        keyboard_input_events.clear();
964    }
965    if egui_wants_input.wants_any_pointer_input() {
966        mouse_input.reset_all();
967        mouse_wheel_events.clear();
968        mouse_button_input_events.clear();
969    }
970
971    for key in pressed.into_iter().flatten() {
972        keyboard_input.press(key);
973    }
974}
975
976/// Stores whether there's an Egui context using pointer or keyboard.
977#[derive(Resource, Clone, Debug, Default)]
978pub struct EguiWantsInput {
979    is_pointer_over_area: bool,
980    wants_pointer_input: bool,
981    is_using_pointer: bool,
982    wants_keyboard_input: bool,
983    is_context_menu_open: bool,
984}
985
986impl EguiWantsInput {
987    /// Is the pointer (mouse/touch) over any egui area?
988    pub fn is_pointer_over_area(&self) -> bool {
989        self.is_pointer_over_area
990    }
991
992    /// True if egui is currently interested in the pointer (mouse or touch).
993    ///
994    /// Could be the pointer is hovering over a [`egui::Window`] or the user is dragging a widget.
995    /// If `false`, the pointer is outside of any egui area and so
996    /// you may be interested in what it is doing (e.g. controlling your game).
997    /// Returns `false` if a drag started outside of egui and then moved over an egui area.
998    pub fn wants_pointer_input(&self) -> bool {
999        self.wants_pointer_input
1000    }
1001
1002    /// Is egui currently using the pointer position (e.g. dragging a slider)?
1003    ///
1004    /// NOTE: this will return `false` if the pointer is just hovering over an egui area.
1005    pub fn is_using_pointer(&self) -> bool {
1006        self.is_using_pointer
1007    }
1008
1009    /// If `true`, egui is currently listening on text input (e.g. typing text in a [`egui::TextEdit`]).
1010    pub fn wants_keyboard_input(&self) -> bool {
1011        self.wants_keyboard_input
1012    }
1013
1014    /// Is an egui context menu open?
1015    pub fn is_context_menu_open(&self) -> bool {
1016        self.is_context_menu_open
1017    }
1018
1019    /// Returns `true` if any of the following is true:
1020    /// [`EguiWantsInput::is_pointer_over_area`], [`EguiWantsInput::wants_pointer_input`], [`EguiWantsInput::is_using_pointer`], [`EguiWantsInput::is_context_menu_open`].
1021    pub fn wants_any_pointer_input(&self) -> bool {
1022        self.is_pointer_over_area
1023            || self.wants_pointer_input
1024            || self.is_using_pointer
1025            || self.is_context_menu_open
1026    }
1027
1028    /// Returns `true` if any of the following is true:
1029    /// [`EguiWantsInput::wants_keyboard_input`], [`EguiWantsInput::is_context_menu_open`].
1030    pub fn wants_any_keyboard_input(&self) -> bool {
1031        self.wants_keyboard_input || self.is_context_menu_open
1032    }
1033
1034    /// Returns `true` if any of the following is true:
1035    /// [`EguiWantsInput::wants_any_pointer_input`], [`EguiWantsInput::wants_any_keyboard_input`].
1036    pub fn wants_any_input(&self) -> bool {
1037        self.wants_any_pointer_input() || self.wants_any_keyboard_input()
1038    }
1039
1040    fn reset(&mut self) {
1041        self.is_pointer_over_area = false;
1042        self.wants_pointer_input = false;
1043        self.is_using_pointer = false;
1044        self.wants_keyboard_input = false;
1045        self.is_context_menu_open = false;
1046    }
1047}
1048
1049/// Updates the [`EguiWantsInput`] resource.
1050pub fn write_egui_wants_input_system(
1051    mut egui_context_query: Query<&mut EguiContext>,
1052    mut egui_wants_input: ResMut<EguiWantsInput>,
1053) {
1054    egui_wants_input.reset();
1055
1056    for mut ctx in egui_context_query.iter_mut() {
1057        let egui_ctx = ctx.get_mut();
1058        egui_wants_input.is_pointer_over_area =
1059            egui_wants_input.is_pointer_over_area || egui_ctx.is_pointer_over_area();
1060        egui_wants_input.wants_pointer_input =
1061            egui_wants_input.wants_pointer_input || egui_ctx.wants_pointer_input();
1062        egui_wants_input.is_using_pointer =
1063            egui_wants_input.is_using_pointer || egui_ctx.is_using_pointer();
1064        egui_wants_input.wants_keyboard_input =
1065            egui_wants_input.wants_keyboard_input || egui_ctx.wants_keyboard_input();
1066        egui_wants_input.is_context_menu_open =
1067            egui_wants_input.is_context_menu_open || egui_ctx.is_context_menu_open();
1068    }
1069}
1070
1071/// Returns `true` if any of the following is true:
1072/// [`EguiWantsInput::is_pointer_over_area`], [`EguiWantsInput::wants_pointer_input`], [`EguiWantsInput::is_using_pointer`], [`EguiWantsInput::is_context_menu_open`].
1073pub fn egui_wants_any_pointer_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1074    egui_wants_input_resource.wants_any_pointer_input()
1075}
1076
1077/// Returns `true` if any of the following is true:
1078/// [`EguiWantsInput::wants_keyboard_input`], [`EguiWantsInput::is_context_menu_open`].
1079pub fn egui_wants_any_keyboard_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1080    egui_wants_input_resource.wants_any_keyboard_input()
1081}
1082
1083/// Returns `true` if any of the following is true:
1084/// [`EguiWantsInput::wants_any_pointer_input`], [`EguiWantsInput::wants_any_keyboard_input`].
1085pub fn egui_wants_any_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
1086    egui_wants_input_resource.wants_any_input()
1087}