diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 6477c90db6dd5..86bf219f31eb7 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -23,6 +23,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" } bevy_input = { path = "../bevy_input", version = "0.14.0-dev" } bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" } bevy_window = { path = "../bevy_window", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index d778d18162df9..cf253e22e9558 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -9,6 +9,7 @@ pub mod accessibility; mod converters; mod system; mod winit_config; +pub mod winit_event; mod winit_windows; use approx::relative_eq; @@ -17,6 +18,7 @@ use bevy_utils::{Duration, Instant}; use system::{changed_windows, create_windows, despawn_windows, CachedWindow}; use winit::dpi::{LogicalSize, PhysicalSize}; pub use winit_config::*; +pub use winit_event::*; pub use winit_windows::*; use bevy_app::{App, AppExit, Last, Plugin, PluginsState}; @@ -117,6 +119,7 @@ impl Plugin for WinitPlugin { app.init_non_send_resource::() .init_resource::() + .add_event::() .set_runner(winit_runner) .add_systems( Last, @@ -158,11 +161,11 @@ impl Plugin for WinitPlugin { } trait AppSendEvent { - fn send_event(&mut self, event: E); + fn send(&mut self, event: impl Into); } -impl AppSendEvent for App { - fn send_event(&mut self, event: E) { - self.world.send_event(event); +impl AppSendEvent for Vec { + fn send(&mut self, event: impl Into) { + self.push(Into::::into(event)); } } @@ -276,6 +279,7 @@ pub fn winit_runner(mut app: App) { let mut create_window = SystemState::>>::from_world(&mut app.world); + let mut winit_events = Vec::default(); // set up the event loop let event_handler = move |event, event_loop: &EventLoopWindowTarget<()>| { handle_winit_event( @@ -286,6 +290,7 @@ pub fn winit_runner(mut app: App) { &mut event_writer_system_state, &mut focused_windows_state, &mut redraw_event_reader, + &mut winit_events, event, event_loop, ); @@ -312,6 +317,7 @@ fn handle_winit_event( )>, focused_windows_state: &mut SystemState<(Res, Query<&Window>)>, redraw_event_reader: &mut ManualEventReader, + winit_events: &mut Vec, event: Event<()>, event_loop: &EventLoopWindowTarget<()>, ) { @@ -388,6 +394,7 @@ fn handle_winit_event( create_window, app_exit_event_reader, redraw_event_reader, + winit_events, ); if runner_state.active != ActiveState::Suspended { event_loop.set_control_flow(ControlFlow::Poll); @@ -432,15 +439,15 @@ fn handle_winit_event( WindowEvent::Resized(size) => { react_to_resize(&mut win, size, &mut window_resized, window); } - WindowEvent::CloseRequested => app.send_event(WindowCloseRequested { window }), + WindowEvent::CloseRequested => winit_events.send(WindowCloseRequested { window }), WindowEvent::KeyboardInput { ref event, .. } => { if event.state.is_pressed() { if let Some(char) = &event.text { let char = char.clone(); - app.send_event(ReceivedCharacter { window, char }); + winit_events.send(ReceivedCharacter { window, char }); } } - app.send_event(converters::convert_keyboard_input(event, window)); + winit_events.send(converters::convert_keyboard_input(event, window)); } WindowEvent::CursorMoved { position, .. } => { let physical_position = DVec2::new(position.x, position.y); @@ -453,35 +460,35 @@ fn handle_winit_event( win.set_physical_cursor_position(Some(physical_position)); let position = (physical_position / win.resolution.scale_factor() as f64).as_vec2(); - app.send_event(CursorMoved { + winit_events.send(CursorMoved { window, position, delta, }); } WindowEvent::CursorEntered { .. } => { - app.send_event(CursorEntered { window }); + winit_events.send(CursorEntered { window }); } WindowEvent::CursorLeft { .. } => { win.set_physical_cursor_position(None); - app.send_event(CursorLeft { window }); + winit_events.send(CursorLeft { window }); } WindowEvent::MouseInput { state, button, .. } => { - app.send_event(MouseButtonInput { + winit_events.send(MouseButtonInput { button: converters::convert_mouse_button(button), state: converters::convert_element_state(state), window, }); } WindowEvent::TouchpadMagnify { delta, .. } => { - app.send_event(TouchpadMagnify(delta as f32)); + winit_events.send(TouchpadMagnify(delta as f32)); } WindowEvent::TouchpadRotate { delta, .. } => { - app.send_event(TouchpadRotate(delta)); + winit_events.send(TouchpadRotate(delta)); } WindowEvent::MouseWheel { delta, .. } => match delta { event::MouseScrollDelta::LineDelta(x, y) => { - app.send_event(MouseWheel { + winit_events.send(MouseWheel { unit: MouseScrollUnit::Line, x, y, @@ -489,7 +496,7 @@ fn handle_winit_event( }); } event::MouseScrollDelta::PixelDelta(p) => { - app.send_event(MouseWheel { + winit_events.send(MouseWheel { unit: MouseScrollUnit::Pixel, x: p.x as f32, y: p.y as f32, @@ -501,7 +508,7 @@ fn handle_winit_event( let location = touch .location .to_logical(win.resolution.scale_factor() as f64); - app.send_event(converters::convert_touch_input(touch, location, window)); + winit_events.send(converters::convert_touch_input(touch, location, window)); } WindowEvent::ScaleFactorChanged { scale_factor, @@ -536,19 +543,19 @@ fn handle_winit_event( win.resolution .set_physical_resolution(new_inner_size.width, new_inner_size.height); - app.send_event(WindowBackendScaleFactorChanged { + winit_events.send(WindowBackendScaleFactorChanged { window, scale_factor, }); if scale_factor_override.is_none() && !relative_eq!(new_factor, prior_factor) { - app.send_event(WindowScaleFactorChanged { + winit_events.send(WindowScaleFactorChanged { window, scale_factor, }); } if !width_equal || !height_equal { - app.send_event(WindowResized { + winit_events.send(WindowResized { window, width: new_logical_width, height: new_logical_height, @@ -557,51 +564,51 @@ fn handle_winit_event( } WindowEvent::Focused(focused) => { win.focused = focused; - app.send_event(WindowFocused { window, focused }); + winit_events.send(WindowFocused { window, focused }); } WindowEvent::Occluded(occluded) => { - app.send_event(WindowOccluded { window, occluded }); + winit_events.send(WindowOccluded { window, occluded }); } WindowEvent::DroppedFile(path_buf) => { - app.send_event(FileDragAndDrop::DroppedFile { window, path_buf }); + winit_events.send(FileDragAndDrop::DroppedFile { window, path_buf }); } WindowEvent::HoveredFile(path_buf) => { - app.send_event(FileDragAndDrop::HoveredFile { window, path_buf }); + winit_events.send(FileDragAndDrop::HoveredFile { window, path_buf }); } WindowEvent::HoveredFileCancelled => { - app.send_event(FileDragAndDrop::HoveredFileCanceled { window }); + winit_events.send(FileDragAndDrop::HoveredFileCanceled { window }); } WindowEvent::Moved(position) => { let position = ivec2(position.x, position.y); win.position.set(position); - app.send_event(WindowMoved { window, position }); + winit_events.send(WindowMoved { window, position }); } WindowEvent::Ime(event) => match event { event::Ime::Preedit(value, cursor) => { - app.send_event(Ime::Preedit { + winit_events.send(Ime::Preedit { window, value, cursor, }); } event::Ime::Commit(value) => { - app.send_event(Ime::Commit { window, value }); + winit_events.send(Ime::Commit { window, value }); } event::Ime::Enabled => { - app.send_event(Ime::Enabled { window }); + winit_events.send(Ime::Enabled { window }); } event::Ime::Disabled => { - app.send_event(Ime::Disabled { window }); + winit_events.send(Ime::Disabled { window }); } }, WindowEvent::ThemeChanged(theme) => { - app.send_event(WindowThemeChanged { + winit_events.send(WindowThemeChanged { window, theme: convert_winit_theme(theme), }); } WindowEvent::Destroyed => { - app.send_event(WindowDestroyed { window }); + winit_events.send(WindowDestroyed { window }); } WindowEvent::RedrawRequested => { run_app_update_if_should( @@ -612,6 +619,7 @@ fn handle_winit_event( create_window, app_exit_event_reader, redraw_event_reader, + winit_events, ); } _ => {} @@ -628,11 +636,11 @@ fn handle_winit_event( runner_state.device_event_received = true; if let DeviceEvent::MouseMotion { delta: (x, y) } = event { let delta = Vec2::new(x as f32, y as f32); - app.send_event(MouseMotion { delta }); + winit_events.send(MouseMotion { delta }); } } Event::Suspended => { - app.send_event(ApplicationLifetime::Suspended); + winit_events.send(ApplicationLifetime::Suspended); // Mark the state as `WillSuspend`. This will let the schedule run one last time // before actually suspending to let the application react runner_state.active = ActiveState::WillSuspend; @@ -647,8 +655,8 @@ fn handle_winit_event( } match runner_state.active { - ActiveState::NotYetStarted => app.send_event(ApplicationLifetime::Started), - _ => app.send_event(ApplicationLifetime::Resumed), + ActiveState::NotYetStarted => winit_events.send(ApplicationLifetime::Started), + _ => winit_events.send(ApplicationLifetime::Resumed), } runner_state.active = ActiveState::Active; runner_state.redraw_requested = true; @@ -692,8 +700,14 @@ fn handle_winit_event( } _ => (), } + + // We drain events after every received winit event in addition to on app update to ensure + // the work of pushing events into event queues is spread out over time in case the app becomes + // dormant for a long stretch. + forward_winit_events(winit_events, app); } +#[allow(clippy::too_many_arguments)] fn run_app_update_if_should( runner_state: &mut WinitAppRunnerState, app: &mut App, @@ -702,12 +716,16 @@ fn run_app_update_if_should( create_window: &mut SystemState>>, app_exit_event_reader: &mut ManualEventReader, redraw_event_reader: &mut ManualEventReader, + winit_events: &mut Vec, ) { runner_state.reset_on_update(); if !runner_state.active.should_run() { return; } + + forward_winit_events(winit_events, app); + if runner_state.active == ActiveState::WillSuspend { runner_state.active = ActiveState::Suspended; #[cfg(target_os = "android")] diff --git a/crates/bevy_winit/src/winit_event.rs b/crates/bevy_winit/src/winit_event.rs new file mode 100644 index 0000000000000..99df2053fd2d8 --- /dev/null +++ b/crates/bevy_winit/src/winit_event.rs @@ -0,0 +1,277 @@ +#![allow(missing_docs)] + +use bevy_app::App; +use bevy_ecs::prelude::*; +use bevy_input::keyboard::KeyboardInput; +use bevy_input::touch::TouchInput; +use bevy_input::{ + mouse::{MouseButtonInput, MouseMotion, MouseWheel}, + touchpad::{TouchpadMagnify, TouchpadRotate}, +}; +use bevy_reflect::Reflect; +use bevy_window::{ + ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, + ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, + WindowCreated, WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized, + WindowScaleFactorChanged, WindowThemeChanged, +}; + +/// Wraps all `bevy_window` events in a common enum. +/// +/// Read these events with `EventReader` if you need to +/// access window events in the order they were received from `winit`. +/// Otherwise, the event types are individually readable with +/// `EventReader` (e.g. `EventReader`). +#[derive(Event, Debug, Clone, PartialEq, Reflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub enum WinitEvent { + ApplicationLifetime(ApplicationLifetime), + CursorEntered(CursorEntered), + CursorLeft(CursorLeft), + CursorMoved(CursorMoved), + FileDragAndDrop(FileDragAndDrop), + Ime(Ime), + ReceivedCharacter(ReceivedCharacter), + RequestRedraw(RequestRedraw), + WindowBackendScaleFactorChanged(WindowBackendScaleFactorChanged), + WindowCloseRequested(WindowCloseRequested), + WindowCreated(WindowCreated), + WindowDestroyed(WindowDestroyed), + WindowFocused(WindowFocused), + WindowMoved(WindowMoved), + WindowOccluded(WindowOccluded), + WindowResized(WindowResized), + WindowScaleFactorChanged(WindowScaleFactorChanged), + WindowThemeChanged(WindowThemeChanged), + + MouseButtonInput(MouseButtonInput), + MouseMotion(MouseMotion), + MouseWheel(MouseWheel), + + TouchpadMagnify(TouchpadMagnify), + TouchpadRotate(TouchpadRotate), + + TouchInput(TouchInput), + + KeyboardInput(KeyboardInput), +} + +impl From for WinitEvent { + fn from(e: ApplicationLifetime) -> Self { + Self::ApplicationLifetime(e) + } +} +impl From for WinitEvent { + fn from(e: CursorEntered) -> Self { + Self::CursorEntered(e) + } +} +impl From for WinitEvent { + fn from(e: CursorLeft) -> Self { + Self::CursorLeft(e) + } +} +impl From for WinitEvent { + fn from(e: CursorMoved) -> Self { + Self::CursorMoved(e) + } +} +impl From for WinitEvent { + fn from(e: FileDragAndDrop) -> Self { + Self::FileDragAndDrop(e) + } +} +impl From for WinitEvent { + fn from(e: Ime) -> Self { + Self::Ime(e) + } +} +impl From for WinitEvent { + fn from(e: ReceivedCharacter) -> Self { + Self::ReceivedCharacter(e) + } +} +impl From for WinitEvent { + fn from(e: RequestRedraw) -> Self { + Self::RequestRedraw(e) + } +} +impl From for WinitEvent { + fn from(e: WindowBackendScaleFactorChanged) -> Self { + Self::WindowBackendScaleFactorChanged(e) + } +} +impl From for WinitEvent { + fn from(e: WindowCloseRequested) -> Self { + Self::WindowCloseRequested(e) + } +} +impl From for WinitEvent { + fn from(e: WindowCreated) -> Self { + Self::WindowCreated(e) + } +} +impl From for WinitEvent { + fn from(e: WindowDestroyed) -> Self { + Self::WindowDestroyed(e) + } +} +impl From for WinitEvent { + fn from(e: WindowFocused) -> Self { + Self::WindowFocused(e) + } +} +impl From for WinitEvent { + fn from(e: WindowMoved) -> Self { + Self::WindowMoved(e) + } +} +impl From for WinitEvent { + fn from(e: WindowOccluded) -> Self { + Self::WindowOccluded(e) + } +} +impl From for WinitEvent { + fn from(e: WindowResized) -> Self { + Self::WindowResized(e) + } +} +impl From for WinitEvent { + fn from(e: WindowScaleFactorChanged) -> Self { + Self::WindowScaleFactorChanged(e) + } +} +impl From for WinitEvent { + fn from(e: WindowThemeChanged) -> Self { + Self::WindowThemeChanged(e) + } +} +impl From for WinitEvent { + fn from(e: MouseButtonInput) -> Self { + Self::MouseButtonInput(e) + } +} +impl From for WinitEvent { + fn from(e: MouseMotion) -> Self { + Self::MouseMotion(e) + } +} +impl From for WinitEvent { + fn from(e: MouseWheel) -> Self { + Self::MouseWheel(e) + } +} +impl From for WinitEvent { + fn from(e: TouchpadMagnify) -> Self { + Self::TouchpadMagnify(e) + } +} +impl From for WinitEvent { + fn from(e: TouchpadRotate) -> Self { + Self::TouchpadRotate(e) + } +} +impl From for WinitEvent { + fn from(e: TouchInput) -> Self { + Self::TouchInput(e) + } +} +impl From for WinitEvent { + fn from(e: KeyboardInput) -> Self { + Self::KeyboardInput(e) + } +} + +/// Forwards buffered [`WinitEvent`] events to the app. +pub(crate) fn forward_winit_events(buffered_events: &mut Vec, app: &mut App) { + if buffered_events.is_empty() { + return; + } + for winit_event in buffered_events.iter() { + match winit_event.clone() { + WinitEvent::ApplicationLifetime(e) => { + app.world.send_event(e); + } + WinitEvent::CursorEntered(e) => { + app.world.send_event(e); + } + WinitEvent::CursorLeft(e) => { + app.world.send_event(e); + } + WinitEvent::CursorMoved(e) => { + app.world.send_event(e); + } + WinitEvent::FileDragAndDrop(e) => { + app.world.send_event(e); + } + WinitEvent::Ime(e) => { + app.world.send_event(e); + } + WinitEvent::ReceivedCharacter(e) => { + app.world.send_event(e); + } + WinitEvent::RequestRedraw(e) => { + app.world.send_event(e); + } + WinitEvent::WindowBackendScaleFactorChanged(e) => { + app.world.send_event(e); + } + WinitEvent::WindowCloseRequested(e) => { + app.world.send_event(e); + } + WinitEvent::WindowCreated(e) => { + app.world.send_event(e); + } + WinitEvent::WindowDestroyed(e) => { + app.world.send_event(e); + } + WinitEvent::WindowFocused(e) => { + app.world.send_event(e); + } + WinitEvent::WindowMoved(e) => { + app.world.send_event(e); + } + WinitEvent::WindowOccluded(e) => { + app.world.send_event(e); + } + WinitEvent::WindowResized(e) => { + app.world.send_event(e); + } + WinitEvent::WindowScaleFactorChanged(e) => { + app.world.send_event(e); + } + WinitEvent::WindowThemeChanged(e) => { + app.world.send_event(e); + } + WinitEvent::MouseButtonInput(e) => { + app.world.send_event(e); + } + WinitEvent::MouseMotion(e) => { + app.world.send_event(e); + } + WinitEvent::MouseWheel(e) => { + app.world.send_event(e); + } + WinitEvent::TouchpadMagnify(e) => { + app.world.send_event(e); + } + WinitEvent::TouchpadRotate(e) => { + app.world.send_event(e); + } + WinitEvent::TouchInput(e) => { + app.world.send_event(e); + } + WinitEvent::KeyboardInput(e) => { + app.world.send_event(e); + } + } + } + app.world + .resource_mut::>() + .send_batch(buffered_events.drain(..)); +}