Skip to content

Commit

Permalink
Add WinitEvent aggregate event for synchronized window event reading (b…
Browse files Browse the repository at this point in the history
…evyengine#12100)

# Objective

- Allow users to read window events in the sequence they appeared. This
is important for precise input handling when there are multiple input
events in a single frame (e.g. click and release vs release and click).

## Solution

- Add a mega-enum `WinitEvent` that collects window events, and send
those alongside the existing more granular window events.

---

## Changelog

- Added `WinitEvent` event that aggregates all window events into a
synchronized event stream.
  • Loading branch information
UkoeHB authored Mar 3, 2024
1 parent faa2ce4 commit ef8a617
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 35 deletions.
1 change: 1 addition & 0 deletions crates/bevy_winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
88 changes: 53 additions & 35 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -117,6 +119,7 @@ impl Plugin for WinitPlugin {

app.init_non_send_resource::<WinitWindows>()
.init_resource::<WinitSettings>()
.add_event::<WinitEvent>()
.set_runner(winit_runner)
.add_systems(
Last,
Expand Down Expand Up @@ -158,11 +161,11 @@ impl Plugin for WinitPlugin {
}

trait AppSendEvent {
fn send_event<E: bevy_ecs::event::Event>(&mut self, event: E);
fn send(&mut self, event: impl Into<WinitEvent>);
}
impl AppSendEvent for App {
fn send_event<E: bevy_ecs::event::Event>(&mut self, event: E) {
self.world.send_event(event);
impl AppSendEvent for Vec<WinitEvent> {
fn send(&mut self, event: impl Into<WinitEvent>) {
self.push(Into::<WinitEvent>::into(event));
}
}

Expand Down Expand Up @@ -276,6 +279,7 @@ pub fn winit_runner(mut app: App) {

let mut create_window =
SystemState::<CreateWindowParams<Added<Window>>>::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(
Expand All @@ -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,
);
Expand All @@ -312,6 +317,7 @@ fn handle_winit_event(
)>,
focused_windows_state: &mut SystemState<(Res<WinitSettings>, Query<&Window>)>,
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
winit_events: &mut Vec<WinitEvent>,
event: Event<()>,
event_loop: &EventLoopWindowTarget<()>,
) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -453,43 +460,43 @@ 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,
window,
});
}
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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -612,6 +619,7 @@ fn handle_winit_event(
create_window,
app_exit_event_reader,
redraw_event_reader,
winit_events,
);
}
_ => {}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -702,12 +716,16 @@ fn run_app_update_if_should(
create_window: &mut SystemState<CreateWindowParams<Added<Window>>>,
app_exit_event_reader: &mut ManualEventReader<AppExit>,
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
winit_events: &mut Vec<WinitEvent>,
) {
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")]
Expand Down
Loading

0 comments on commit ef8a617

Please sign in to comment.