bevy_egui/
input.rs

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