#[cfg(target_arch = "wasm32")]
use crate::text_agent::{is_mobile_safari, update_text_agent};
use crate::{
EguiContext, EguiContextSettings, EguiGlobalSettings, EguiInput, EguiOutput,
helpers::{QueryHelper, vec2_into_egui_pos2},
};
use bevy_ecs::{
message::MessageIterator,
prelude::*,
system::{NonSendMarker, SystemParam},
};
use bevy_input::{
ButtonInput, ButtonState,
keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput},
mouse::{MouseButton, MouseButtonInput, MouseScrollUnit, MouseWheel},
touch::TouchInput,
};
use bevy_log::{self as log};
use bevy_time::{Real, Time};
use bevy_window::{CursorMoved, FileDragAndDrop, Ime, Window};
use bevy_winit::WinitUserEvent;
use egui::Modifiers;
#[derive(Component, Default)]
pub struct EguiContextPointerPosition {
pub position: egui::Pos2,
}
#[derive(Component, Default)]
pub struct EguiContextPointerTouchId {
pub pointer_touch_id: Option<u64>,
}
#[derive(Component, Default)]
pub struct EguiContextImeState {
pub has_sent_ime_enabled: bool,
pub is_ime_allowed: bool,
pub ime_rect: Option<egui::Rect>,
}
#[derive(Message)]
pub struct EguiInputEvent {
pub context: Entity,
pub event: egui::Event,
}
#[derive(Message)]
pub struct EguiFileDragAndDropMessage {
pub context: Entity,
pub message: FileDragAndDrop,
}
#[derive(Resource, Clone)]
pub struct HoveredNonWindowEguiContext(pub Entity);
#[derive(Resource, Clone)]
pub struct FocusedNonWindowEguiContext(pub Entity);
#[derive(Resource, Clone, Copy, Debug)]
pub struct ModifierKeysState {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub win: bool,
is_macos: bool,
}
impl Default for ModifierKeysState {
fn default() -> Self {
let mut state = Self {
shift: false,
ctrl: false,
alt: false,
win: false,
is_macos: false,
};
#[cfg(not(target_arch = "wasm32"))]
{
state.is_macos = cfg!(target_os = "macos");
}
#[cfg(target_arch = "wasm32")]
if let Some(window) = web_sys::window()
&& let Ok(user_agent) = window.navigator().user_agent()
&& user_agent.to_ascii_lowercase().contains("mac")
{
state.is_macos = true;
}
state
}
}
impl ModifierKeysState {
pub fn to_egui_modifiers(&self) -> egui::Modifiers {
egui::Modifiers {
alt: self.alt,
ctrl: self.ctrl,
shift: self.shift,
mac_cmd: if self.is_macos { self.win } else { false },
command: if self.is_macos { self.win } else { self.ctrl },
}
}
pub fn text_input_is_allowed(&self) -> bool {
!self.win && !self.ctrl || !self.is_macos && self.ctrl && self.alt
}
fn reset(&mut self) {
self.shift = false;
self.ctrl = false;
self.alt = false;
self.win = false;
}
}
#[derive(Resource, Default)]
pub struct WindowToEguiContextMap {
pub window_to_contexts:
bevy_platform::collections::HashMap<Entity, bevy_platform::collections::HashSet<Entity>>,
pub context_to_window: bevy_platform::collections::HashMap<Entity, Entity>,
}
impl WindowToEguiContextMap {
pub fn on_egui_context_added_system(
mut res: ResMut<Self>,
added_contexts: Query<
(Entity, &bevy_camera::RenderTarget, &mut EguiContext),
Added<EguiContext>,
>,
primary_window: Query<Entity, With<bevy_window::PrimaryWindow>>,
event_loop_proxy: Option<Res<bevy_winit::EventLoopProxyWrapper>>,
) {
for (egui_context_entity, render_target, mut egui_context) in added_contexts {
if let bevy_camera::RenderTarget::Window(window_ref) = render_target
&& let Some(window_ref) = window_ref.normalize(primary_window.single().ok())
{
res.window_to_contexts
.entry(window_ref.entity())
.or_default()
.insert(egui_context_entity);
res.context_to_window
.insert(egui_context_entity, window_ref.entity());
let Some(event_loop_proxy) = &event_loop_proxy else {
continue;
};
let message_loop_proxy = (*event_loop_proxy).clone();
egui_context
.get_mut()
.set_request_repaint_callback(move |repaint_info| {
if repaint_info.delay.is_zero() {
log::trace!("Sending the WakeUp message");
let _ = message_loop_proxy.send_event(WinitUserEvent::WakeUp);
}
});
}
}
}
pub fn on_egui_context_removed_system(
mut res: ResMut<Self>,
mut removed_contexts: RemovedComponents<EguiContext>,
) {
for egui_context_entity in removed_contexts.read() {
let Some(window_entity) = res.context_to_window.remove(&egui_context_entity) else {
continue;
};
let Some(window_contexts) = res.window_to_contexts.get_mut(&window_entity) else {
log::warn!(
"A destroyed Egui context's window isn't registered: {egui_context_entity:?}"
);
continue;
};
window_contexts.remove(&egui_context_entity);
}
}
}
pub struct EguiContextsMessageIterator<'a, M: Message, F> {
message_iter: MessageIterator<'a, M>,
map_message_to_window_id_f: F,
current_message: Option<&'a M>,
current_message_contexts: Vec<Entity>,
non_window_context: Option<Entity>,
map: &'a WindowToEguiContextMap,
}
impl<'a, M: Message, F: FnMut(&'a M) -> Entity> Iterator for EguiContextsMessageIterator<'a, M, F> {
type Item = (&'a M, Entity);
fn next(&mut self) -> Option<Self::Item> {
if self.current_message_contexts.is_empty() {
self.current_message = None;
}
if self.current_message.is_none() {
self.current_message = self.message_iter.next();
if self.non_window_context.is_some() {
return self.current_message.zip(self.non_window_context);
}
if let Some(current) = self.current_message
&& let Some(contexts) = self
.map
.window_to_contexts
.get(&(self.map_message_to_window_id_f)(current))
{
self.current_message_contexts = contexts.iter().cloned().collect();
}
}
self.current_message
.zip(self.current_message_contexts.pop())
}
}
#[derive(SystemParam)]
pub struct EguiContextMessageReader<'w, 's, M: Message> {
message_reader: MessageReader<'w, 's, M>,
map: Res<'w, WindowToEguiContextMap>,
hovered_non_window_egui_context: Option<Res<'w, HoveredNonWindowEguiContext>>,
focused_non_window_egui_context: Option<Res<'w, FocusedNonWindowEguiContext>>,
}
impl<'w, 's, M: Message> EguiContextMessageReader<'w, 's, M> {
pub fn read<'a, F>(
&'a mut self,
map_message_to_window_id_f: F,
) -> EguiContextsMessageIterator<'a, M, F>
where
F: FnMut(&'a M) -> Entity,
M: Message,
{
EguiContextsMessageIterator {
message_iter: self.message_reader.read(),
map_message_to_window_id_f,
current_message: None,
current_message_contexts: Vec::new(),
non_window_context: None,
map: &self.map,
}
}
pub fn read_with_non_window_hovered<'a, F>(
&'a mut self,
map_message_to_window_id_f: F,
) -> EguiContextsMessageIterator<'a, M, F>
where
F: FnMut(&'a M) -> Entity,
M: Message,
{
EguiContextsMessageIterator {
message_iter: self.message_reader.read(),
map_message_to_window_id_f,
current_message: None,
current_message_contexts: Vec::new(),
non_window_context: self
.hovered_non_window_egui_context
.as_deref()
.map(|context| context.0),
map: &self.map,
}
}
pub fn read_with_non_window_focused<'a, F>(
&'a mut self,
map_message_to_window_id_f: F,
) -> EguiContextsMessageIterator<'a, M, F>
where
F: FnMut(&'a M) -> Entity,
M: Message,
{
EguiContextsMessageIterator {
message_iter: self.message_reader.read(),
map_message_to_window_id_f,
current_message: None,
current_message_contexts: Vec::new(),
non_window_context: self
.focused_non_window_egui_context
.as_deref()
.map(|context| context.0),
map: &self.map,
}
}
}
pub fn write_modifiers_keys_state_system(
mut keyboard_input_reader: MessageReader<KeyboardInput>,
mut focus_reader: MessageReader<KeyboardFocusLost>,
mut modifier_keys_state: ResMut<ModifierKeysState>,
) {
if !focus_reader.is_empty() {
focus_reader.clear();
modifier_keys_state.reset();
}
for message in keyboard_input_reader.read() {
let KeyboardInput {
logical_key, state, ..
} = message;
match logical_key {
Key::Shift => {
modifier_keys_state.shift = state.is_pressed();
}
Key::Control => {
modifier_keys_state.ctrl = state.is_pressed();
}
Key::Alt => {
modifier_keys_state.alt = state.is_pressed();
}
Key::Super | Key::Meta => {
modifier_keys_state.win = state.is_pressed();
}
_ => {}
};
}
}
pub fn write_window_pointer_moved_messages_system(
mut cursor_moved_reader: EguiContextMessageReader<CursorMoved>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
mut egui_contexts: Query<
(&EguiContextSettings, &mut EguiContextPointerPosition),
With<EguiContext>,
>,
) {
for (message, context) in cursor_moved_reader.read(|message| message.window) {
let Some((context_settings, mut context_pointer_position)) =
egui_contexts.get_some_mut(context)
else {
continue;
};
if !context_settings
.input_system_settings
.run_write_window_pointer_moved_messages_system
{
continue;
}
let scale_factor = context_settings.scale_factor;
let pointer_position = vec2_into_egui_pos2(message.position / scale_factor);
context_pointer_position.position = pointer_position;
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerMoved(pointer_position),
});
}
}
pub fn write_pointer_button_messages_system(
egui_global_settings: Res<EguiGlobalSettings>,
mut commands: Commands,
modifier_keys_state: Res<ModifierKeysState>,
mut mouse_button_input_reader: EguiContextMessageReader<MouseButtonInput>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
) {
let modifiers = modifier_keys_state.to_egui_modifiers();
let hovered_non_window_egui_context = mouse_button_input_reader
.hovered_non_window_egui_context
.as_deref()
.cloned();
for (message, context) in
mouse_button_input_reader.read_with_non_window_hovered(|message| message.window)
{
let Some((context_settings, context_pointer_position)) = egui_contexts.get_some(context)
else {
continue;
};
if !context_settings
.input_system_settings
.run_write_pointer_button_messages_system
{
continue;
}
let button = match message.button {
MouseButton::Left => Some(egui::PointerButton::Primary),
MouseButton::Right => Some(egui::PointerButton::Secondary),
MouseButton::Middle => Some(egui::PointerButton::Middle),
MouseButton::Back => Some(egui::PointerButton::Extra1),
MouseButton::Forward => Some(egui::PointerButton::Extra2),
_ => None,
};
let Some(button) = button else {
continue;
};
let pressed = match message.state {
ButtonState::Pressed => true,
ButtonState::Released => false,
};
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerButton {
pos: context_pointer_position.position,
button,
pressed,
modifiers,
},
});
if egui_global_settings.enable_focused_non_window_context_updates && pressed {
if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
commands.insert_resource(FocusedNonWindowEguiContext(
hovered_non_window_egui_context.0,
));
} else {
commands.remove_resource::<FocusedNonWindowEguiContext>();
}
}
}
}
pub fn write_non_window_pointer_moved_messages_system(
hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
mut cursor_moved_reader: MessageReader<CursorMoved>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
egui_contexts: Query<(&EguiContextSettings, &EguiContextPointerPosition), With<EguiContext>>,
) {
if cursor_moved_reader.is_empty() {
return;
}
cursor_moved_reader.clear();
let Some(HoveredNonWindowEguiContext(hovered_non_window_egui_context)) =
hovered_non_window_egui_context.as_deref()
else {
return;
};
let Some((context_settings, context_pointer_position)) =
egui_contexts.get_some(*hovered_non_window_egui_context)
else {
return;
};
if !context_settings
.input_system_settings
.run_write_non_window_pointer_moved_messages_system
{
return;
}
egui_input_message_writer.write(EguiInputEvent {
context: *hovered_non_window_egui_context,
event: egui::Event::PointerMoved(context_pointer_position.position),
});
}
pub fn write_mouse_wheel_messages_system(
modifier_keys_state: Res<ModifierKeysState>,
mut mouse_wheel_reader: EguiContextMessageReader<MouseWheel>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
) {
let modifiers = modifier_keys_state.to_egui_modifiers();
for (message, context) in
mouse_wheel_reader.read_with_non_window_hovered(|message| message.window)
{
let delta = egui::vec2(message.x, message.y);
let unit = match message.unit {
MouseScrollUnit::Line => egui::MouseWheelUnit::Line,
MouseScrollUnit::Pixel => egui::MouseWheelUnit::Point,
};
let Some(context_settings) = egui_contexts.get_some(context) else {
continue;
};
if !context_settings
.input_system_settings
.run_write_mouse_wheel_messages_system
{
continue;
}
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::MouseWheel {
unit,
delta,
modifiers,
},
});
}
}
pub fn write_keyboard_input_messages_system(
modifier_keys_state: Res<ModifierKeysState>,
#[cfg(all(
feature = "manage_clipboard",
not(target_os = "android"),
not(target_arch = "wasm32")
))]
mut egui_clipboard: ResMut<crate::EguiClipboard>,
mut keyboard_input_reader: EguiContextMessageReader<KeyboardInput>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
) {
let modifiers = modifier_keys_state.to_egui_modifiers();
for (message, context) in
keyboard_input_reader.read_with_non_window_focused(|message| message.window)
{
let Some(context_settings) = egui_contexts.get_some(context) else {
continue;
};
if !context_settings
.input_system_settings
.run_write_keyboard_input_messages_system
{
continue;
}
if modifier_keys_state.text_input_is_allowed() && message.state.is_pressed() {
match &message.logical_key {
Key::Character(char) if char.matches(char::is_control).count() == 0 => {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Text(char.to_string()),
});
}
Key::Space => {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Text(" ".to_string()),
});
}
_ => (),
}
}
let key = crate::helpers::bevy_to_egui_key(&message.logical_key);
let physical_key = crate::helpers::bevy_to_egui_physical_key(&message.key_code);
let (Some(key), physical_key) = (key.or(physical_key), physical_key) else {
continue;
};
let egui_message = egui::Event::Key {
key,
pressed: message.state.is_pressed(),
repeat: false,
modifiers,
physical_key,
};
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui_message,
});
#[cfg(all(
feature = "manage_clipboard",
not(target_os = "android"),
not(target_arch = "wasm32")
))]
if modifiers.command && message.state.is_pressed() {
match key {
egui::Key::C => {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Copy,
});
}
egui::Key::X => {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Cut,
});
}
egui::Key::V => {
if let Some(contents) = egui_clipboard.get_text() {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Text(contents),
});
}
}
_ => {}
}
}
}
}
pub fn write_ime_messages_system(
mut ime_reader: EguiContextMessageReader<Ime>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
mut egui_contexts: Query<
(
Entity,
&EguiContextSettings,
&mut EguiContextImeState,
&EguiOutput,
),
With<EguiContext>,
>,
) {
for (message, context) in ime_reader.read_with_non_window_focused(|message| match &message {
Ime::Preedit { window, .. }
| Ime::Commit { window, .. }
| Ime::Disabled { window }
| Ime::Enabled { window } => *window,
}) {
let Some((_entity, context_settings, mut ime_state, _egui_output)) =
egui_contexts.get_some_mut(context)
else {
continue;
};
if !context_settings
.input_system_settings
.run_write_ime_messages_system
|| !context_settings.enable_ime
{
continue;
}
let ime_message_enable =
|ime_state: &mut EguiContextImeState,
egui_input_message_writer: &mut MessageWriter<EguiInputEvent>| {
if !ime_state.has_sent_ime_enabled {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Ime(egui::ImeEvent::Enabled),
});
ime_state.has_sent_ime_enabled = true;
}
};
let ime_message_disable =
|ime_state: &mut EguiContextImeState,
egui_input_message_writer: &mut MessageWriter<EguiInputEvent>| {
if !ime_state.has_sent_ime_enabled {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Ime(egui::ImeEvent::Disabled),
});
ime_state.has_sent_ime_enabled = false;
}
};
match message {
Ime::Enabled { window: _ } => {
if cfg!(target_os = "linux") {
} else {
ime_message_enable(&mut ime_state, &mut egui_input_message_writer);
}
}
Ime::Preedit {
value,
window: _,
cursor: Some(_),
} => {
ime_message_enable(&mut ime_state, &mut egui_input_message_writer);
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Ime(egui::ImeEvent::Preedit(value.clone())),
});
}
Ime::Commit { value, window: _ } => {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Ime(egui::ImeEvent::Commit(value.clone())),
});
ime_message_disable(&mut ime_state, &mut egui_input_message_writer);
}
Ime::Disabled { window: _ }
| Ime::Preedit {
cursor: None,
window: _,
value: _,
} => {
ime_message_disable(&mut ime_state, &mut egui_input_message_writer);
}
}
}
}
pub fn process_ime_system(
mut egui_context_query: Query<(
Entity,
&EguiOutput,
&EguiContextSettings,
&mut EguiContext,
&mut EguiContextImeState,
)>,
window_to_egui_context_map: Res<WindowToEguiContextMap>,
_non_send_marker: NonSendMarker,
) {
for (entity, egui_output, egui_settings, mut egui_context, mut egui_ime_state) in
&mut egui_context_query
{
if !egui_settings.enable_ime {
continue;
}
let Some(window_entity) = window_to_egui_context_map.context_to_window.get(&entity) else {
continue;
};
bevy_winit::WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
let Some(winit_window) = winit_windows.get_window(*window_entity) else {
log::warn!(
"Cannot access an underlying winit window for a window entity {}",
window_entity
);
return;
};
let ime_allowed = egui_output.platform_output.ime.is_some();
if ime_allowed != egui_ime_state.is_ime_allowed {
winit_window.set_ime_allowed(ime_allowed);
egui_ime_state.is_ime_allowed = ime_allowed;
}
if let Some(ime) = egui_output.platform_output.ime {
let ime_rect_px = ime.rect * egui_settings.scale_factor;
if egui_ime_state.ime_rect != Some(ime_rect_px)
|| egui_context.get_mut().input(|i| !i.events.is_empty())
{
egui_ime_state.ime_rect = Some(ime_rect_px);
winit_window.set_ime_cursor_area(
winit::dpi::LogicalPosition {
x: ime_rect_px.min.x,
y: ime_rect_px.min.y,
},
winit::dpi::LogicalSize {
width: ime_rect_px.width(),
height: ime_rect_px.height(),
},
);
}
} else {
egui_ime_state.ime_rect = None;
}
});
}
}
pub fn write_file_dnd_messages_system(
mut dnd_reader: EguiContextMessageReader<FileDragAndDrop>,
mut egui_file_dnd_message_writer: MessageWriter<EguiFileDragAndDropMessage>,
egui_contexts: Query<&EguiContextSettings, With<EguiContext>>,
) {
for (message, context) in dnd_reader.read_with_non_window_hovered(|message| match &message {
FileDragAndDrop::DroppedFile { window, .. }
| FileDragAndDrop::HoveredFile { window, .. }
| FileDragAndDrop::HoveredFileCanceled { window } => *window,
}) {
let Some(context_settings) = egui_contexts.get_some(context) else {
continue;
};
if !context_settings
.input_system_settings
.run_write_file_dnd_messages_system
{
continue;
}
match message {
FileDragAndDrop::DroppedFile { window, path_buf } => {
egui_file_dnd_message_writer.write(EguiFileDragAndDropMessage {
context,
message: FileDragAndDrop::DroppedFile {
window: *window,
path_buf: path_buf.clone(),
},
});
}
FileDragAndDrop::HoveredFile { window, path_buf } => {
egui_file_dnd_message_writer.write(EguiFileDragAndDropMessage {
context,
message: FileDragAndDrop::HoveredFile {
window: *window,
path_buf: path_buf.clone(),
},
});
}
FileDragAndDrop::HoveredFileCanceled { window } => {
egui_file_dnd_message_writer.write(EguiFileDragAndDropMessage {
context,
message: FileDragAndDrop::HoveredFileCanceled { window: *window },
});
}
}
}
}
pub fn write_window_touch_messages_system(
mut commands: Commands,
egui_global_settings: Res<EguiGlobalSettings>,
modifier_keys_state: Res<ModifierKeysState>,
mut touch_input_reader: EguiContextMessageReader<TouchInput>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
mut egui_contexts: Query<
(
&EguiContextSettings,
&mut EguiContextPointerPosition,
&mut EguiContextPointerTouchId,
&EguiOutput,
),
With<EguiContext>,
>,
) {
let modifiers = modifier_keys_state.to_egui_modifiers();
let hovered_non_window_egui_context = touch_input_reader
.hovered_non_window_egui_context
.as_deref()
.cloned();
for (message, context) in touch_input_reader.read(|message| message.window) {
let Some((
context_settings,
mut context_pointer_position,
mut context_pointer_touch_id,
output,
)) = egui_contexts.get_some_mut(context)
else {
continue;
};
if egui_global_settings.enable_focused_non_window_context_updates
&& let bevy_input::touch::TouchPhase::Started = message.phase
{
if let Some(hovered_non_window_egui_context) = &hovered_non_window_egui_context {
if let bevy_input::touch::TouchPhase::Started = message.phase {
commands.insert_resource(FocusedNonWindowEguiContext(
hovered_non_window_egui_context.0,
));
}
continue;
}
commands.remove_resource::<FocusedNonWindowEguiContext>();
}
if !context_settings
.input_system_settings
.run_write_window_touch_messages_system
{
continue;
}
let scale_factor = context_settings.scale_factor;
let touch_position = vec2_into_egui_pos2(message.position / scale_factor);
context_pointer_position.position = touch_position;
write_touch_message(
&mut egui_input_message_writer,
message,
context,
output,
touch_position,
modifiers,
&mut context_pointer_touch_id,
);
}
}
pub fn write_non_window_touch_messages_system(
focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
mut touch_input_reader: MessageReader<TouchInput>,
mut egui_input_message_writer: MessageWriter<EguiInputEvent>,
modifier_keys_state: Res<ModifierKeysState>,
mut egui_contexts: Query<
(
&EguiContextSettings,
&EguiContextPointerPosition,
&mut EguiContextPointerTouchId,
&EguiOutput,
),
With<EguiContext>,
>,
) {
let modifiers = modifier_keys_state.to_egui_modifiers();
for message in touch_input_reader.read() {
let Some(&FocusedNonWindowEguiContext(focused_non_window_egui_context)) =
focused_non_window_egui_context.as_deref()
else {
continue;
};
let Some((
context_settings,
context_pointer_position,
mut context_pointer_touch_id,
output,
)) = egui_contexts.get_some_mut(focused_non_window_egui_context)
else {
continue;
};
if !context_settings
.input_system_settings
.run_write_non_window_touch_messages_system
{
continue;
}
write_touch_message(
&mut egui_input_message_writer,
message,
focused_non_window_egui_context,
output,
context_pointer_position.position,
modifiers,
&mut context_pointer_touch_id,
);
}
}
fn write_touch_message(
egui_input_message_writer: &mut MessageWriter<EguiInputEvent>,
message: &TouchInput,
context: Entity,
_output: &EguiOutput,
pointer_position: egui::Pos2,
modifiers: Modifiers,
context_pointer_touch_id: &mut EguiContextPointerTouchId,
) {
let touch_id = egui::TouchId::from(message.id);
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::Touch {
device_id: egui::TouchDeviceId(message.window.to_bits()),
id: touch_id,
phase: match message.phase {
bevy_input::touch::TouchPhase::Started => egui::TouchPhase::Start,
bevy_input::touch::TouchPhase::Moved => egui::TouchPhase::Move,
bevy_input::touch::TouchPhase::Ended => egui::TouchPhase::End,
bevy_input::touch::TouchPhase::Canceled => egui::TouchPhase::Cancel,
},
pos: pointer_position,
force: match message.force {
Some(bevy_input::touch::ForceTouch::Normalized(force)) => Some(force as f32),
Some(bevy_input::touch::ForceTouch::Calibrated {
force,
max_possible_force,
..
}) => Some((force / max_possible_force) as f32),
None => None,
},
},
});
if context_pointer_touch_id.pointer_touch_id.is_none()
|| context_pointer_touch_id.pointer_touch_id.unwrap() == message.id
{
match message.phase {
bevy_input::touch::TouchPhase::Started => {
context_pointer_touch_id.pointer_touch_id = Some(message.id);
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerMoved(pointer_position),
});
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerButton {
pos: pointer_position,
button: egui::PointerButton::Primary,
pressed: true,
modifiers,
},
});
}
bevy_input::touch::TouchPhase::Moved => {
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerMoved(pointer_position),
});
}
bevy_input::touch::TouchPhase::Ended => {
context_pointer_touch_id.pointer_touch_id = None;
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerButton {
pos: pointer_position,
button: egui::PointerButton::Primary,
pressed: false,
modifiers,
},
});
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerGone,
});
#[cfg(target_arch = "wasm32")]
if !is_mobile_safari() {
update_text_agent(
_output.platform_output.ime.is_some()
|| _output.platform_output.mutable_text_under_cursor,
);
}
}
bevy_input::touch::TouchPhase::Canceled => {
context_pointer_touch_id.pointer_touch_id = None;
egui_input_message_writer.write(EguiInputEvent {
context,
event: egui::Event::PointerGone,
});
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn write_egui_input_system(
focused_non_window_egui_context: Option<Res<FocusedNonWindowEguiContext>>,
window_to_egui_context_map: Res<WindowToEguiContextMap>,
modifier_keys_state: Res<ModifierKeysState>,
mut egui_input_reader: MessageReader<EguiInputEvent>,
mut egui_file_dnd_message_reader: MessageReader<EguiFileDragAndDropMessage>,
mut egui_contexts: Query<(Entity, &mut EguiInput)>,
windows: Query<&Window>,
time: Res<Time<Real>>,
) {
for EguiInputEvent { context, event } in egui_input_reader.read() {
#[cfg(feature = "log_input_messages")]
log::warn!("{context:?}: {message:?}");
let (_, mut egui_input) = match egui_contexts.get_mut(*context) {
Ok(egui_input) => egui_input,
Err(err) => {
log::error!(
"Failed to get an Egui context ({context:?}) for an event ({event:?}): {err:?}"
);
continue;
}
};
egui_input.events.push(event.clone());
}
for EguiFileDragAndDropMessage { context, message } in egui_file_dnd_message_reader.read() {
#[cfg(feature = "log_file_dnd_messages")]
log::warn!("{context:?}: {message:?}");
let (_, mut egui_input) = match egui_contexts.get_mut(*context) {
Ok(egui_input) => egui_input,
Err(err) => {
log::error!(
"Failed to get an Egui context ({context:?}) for an message ({message:?}): {err:?}"
);
continue;
}
};
match message {
FileDragAndDrop::DroppedFile {
window: _,
path_buf,
} => {
egui_input.hovered_files.clear();
egui_input.dropped_files.push(egui::DroppedFile {
path: Some(path_buf.clone()),
..Default::default()
});
}
FileDragAndDrop::HoveredFile {
window: _,
path_buf,
} => {
egui_input.hovered_files.push(egui::HoveredFile {
path: Some(path_buf.clone()),
..Default::default()
});
}
FileDragAndDrop::HoveredFileCanceled { window: _ } => {
egui_input.hovered_files.clear();
}
}
}
for (entity, mut egui_input) in egui_contexts.iter_mut() {
egui_input.focused = focused_non_window_egui_context.as_deref().map_or_else(
|| {
window_to_egui_context_map
.context_to_window
.get(&entity)
.and_then(|window_entity| windows.get_some(*window_entity))
.is_some_and(|window| window.focused)
},
|context| context.0 == entity,
);
egui_input.modifiers = modifier_keys_state.to_egui_modifiers();
egui_input.time = Some(time.elapsed_secs_f64());
}
}
pub fn absorb_bevy_input_system(
egui_wants_input: Res<EguiWantsInput>,
mut mouse_input: ResMut<ButtonInput<MouseButton>>,
mut keyboard_input: ResMut<ButtonInput<KeyCode>>,
mut keyboard_input_messages: ResMut<Messages<KeyboardInput>>,
mut mouse_wheel_messages: ResMut<Messages<MouseWheel>>,
mut mouse_button_input_messages: ResMut<Messages<MouseButtonInput>>,
) {
let modifiers = [
KeyCode::SuperLeft,
KeyCode::SuperRight,
KeyCode::ControlLeft,
KeyCode::ControlRight,
KeyCode::AltLeft,
KeyCode::AltRight,
KeyCode::ShiftLeft,
KeyCode::ShiftRight,
];
let pressed = modifiers.map(|key| keyboard_input.pressed(key).then_some(key));
if egui_wants_input.wants_any_keyboard_input() {
keyboard_input.reset_all();
keyboard_input_messages.clear();
}
if egui_wants_input.wants_any_pointer_input() {
mouse_input.reset_all();
mouse_wheel_messages.clear();
mouse_button_input_messages.clear();
}
for key in pressed.into_iter().flatten() {
keyboard_input.press(key);
}
}
#[derive(Resource, Clone, Debug, Default)]
pub struct EguiWantsInput {
is_pointer_over_area: bool,
wants_pointer_input: bool,
is_using_pointer: bool,
wants_keyboard_input: bool,
is_popup_open: bool,
}
impl EguiWantsInput {
pub fn is_pointer_over_area(&self) -> bool {
self.is_pointer_over_area
}
pub fn wants_pointer_input(&self) -> bool {
self.wants_pointer_input
}
pub fn is_using_pointer(&self) -> bool {
self.is_using_pointer
}
pub fn wants_keyboard_input(&self) -> bool {
self.wants_keyboard_input
}
#[deprecated = "use is_popup_open, renamed upstream in egui"]
pub fn is_context_menu_open(&self) -> bool {
self.is_popup_open
}
pub fn is_popup_open(&self) -> bool {
self.is_popup_open
}
pub fn wants_any_pointer_input(&self) -> bool {
self.is_pointer_over_area
|| self.wants_pointer_input
|| self.is_using_pointer
|| self.is_popup_open
}
pub fn wants_any_keyboard_input(&self) -> bool {
self.wants_keyboard_input || self.is_popup_open
}
pub fn wants_any_input(&self) -> bool {
self.wants_any_pointer_input() || self.wants_any_keyboard_input()
}
fn reset(&mut self) {
self.is_pointer_over_area = false;
self.wants_pointer_input = false;
self.is_using_pointer = false;
self.wants_keyboard_input = false;
self.is_popup_open = false;
}
}
pub fn write_egui_wants_input_system(
mut egui_context_query: Query<&mut EguiContext>,
mut egui_wants_input: ResMut<EguiWantsInput>,
) {
egui_wants_input.reset();
for mut ctx in egui_context_query.iter_mut() {
let egui_ctx = ctx.get_mut();
egui_wants_input.is_pointer_over_area =
egui_wants_input.is_pointer_over_area || egui_ctx.is_pointer_over_area();
egui_wants_input.wants_pointer_input =
egui_wants_input.wants_pointer_input || egui_ctx.wants_pointer_input();
egui_wants_input.is_using_pointer =
egui_wants_input.is_using_pointer || egui_ctx.is_using_pointer();
egui_wants_input.wants_keyboard_input =
egui_wants_input.wants_keyboard_input || egui_ctx.wants_keyboard_input();
egui_wants_input.is_popup_open = egui_wants_input.is_popup_open || egui_ctx.is_popup_open();
}
}
pub fn egui_wants_any_pointer_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
egui_wants_input_resource.wants_any_pointer_input()
}
pub fn egui_wants_any_keyboard_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
egui_wants_input_resource.wants_any_keyboard_input()
}
pub fn egui_wants_any_input(egui_wants_input_resource: Res<EguiWantsInput>) -> bool {
egui_wants_input_resource.wants_any_input()
}