use approx::relative_eq;
use bevy_app::{App, AppExit, PluginsState};
use bevy_ecs::{
change_detection::{DetectChanges, Res},
entity::Entity,
message::{MessageCursor, MessageWriter},
prelude::*,
system::SystemState,
world::FromWorld,
};
use bevy_input::{
gestures::*,
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
};
use bevy_log::{trace, warn};
use bevy_math::{ivec2, DVec2, Vec2};
use bevy_platform::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::tick_global_task_pools_on_main_thread;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::EventLoopExtWebSys;
use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
event,
event::{DeviceEvent, DeviceId, StartCause, WindowEvent},
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::WindowId,
};
use bevy_window::{
AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, RequestRedraw,
Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed,
WindowEvent as BevyWindowEvent, WindowFocused, WindowMoved, WindowOccluded, WindowResized,
WindowScaleFactorChanged, WindowThemeChanged,
};
#[cfg(target_os = "android")]
use bevy_window::{CursorOptions, PrimaryWindow, RawHandleWrapper};
use crate::{
accessibility::ACCESS_KIT_ADAPTERS,
converters, create_windows,
system::{create_monitors, CachedWindow, WinitWindowPressedKeys},
AppSendEvent, CreateMonitorParams, CreateWindowParams, RawWinitWindowEvent, UpdateMode,
WinitSettings, WinitUserEvent, WINIT_WINDOWS,
};
pub(crate) struct WinitAppRunnerState {
app: App,
app_exit: Option<AppExit>,
update_mode: UpdateMode,
window_event_received: bool,
device_event_received: bool,
user_event_received: bool,
redraw_requested: bool,
ran_update_since_last_redraw: bool,
wait_elapsed: bool,
startup_forced_updates: u32,
lifecycle: AppLifecycle,
previous_lifecycle: AppLifecycle,
bevy_window_events: Vec<bevy_window::WindowEvent>,
raw_winit_events: Vec<RawWinitWindowEvent>,
message_writer_system_state: SystemState<(
MessageWriter<'static, WindowResized>,
MessageWriter<'static, WindowBackendScaleFactorChanged>,
MessageWriter<'static, WindowScaleFactorChanged>,
Query<
'static,
'static,
(
&'static mut Window,
&'static mut CachedWindow,
&'static mut WinitWindowPressedKeys,
),
>,
)>,
scheduled_tick_start: Option<Instant>,
}
impl WinitAppRunnerState {
fn new(mut app: App) -> Self {
let message_writer_system_state: SystemState<(
MessageWriter<WindowResized>,
MessageWriter<WindowBackendScaleFactorChanged>,
MessageWriter<WindowScaleFactorChanged>,
Query<(&mut Window, &mut CachedWindow, &mut WinitWindowPressedKeys)>,
)> = SystemState::new(app.world_mut());
Self {
app,
lifecycle: AppLifecycle::Idle,
previous_lifecycle: AppLifecycle::Idle,
app_exit: None,
update_mode: UpdateMode::Continuous,
window_event_received: false,
device_event_received: false,
user_event_received: false,
redraw_requested: false,
ran_update_since_last_redraw: false,
wait_elapsed: false,
startup_forced_updates: 5,
bevy_window_events: Vec::new(),
raw_winit_events: Vec::new(),
message_writer_system_state,
scheduled_tick_start: None,
}
}
fn reset_on_update(&mut self) {
self.window_event_received = false;
self.device_event_received = false;
self.user_event_received = false;
}
fn world(&self) -> &World {
self.app.world()
}
pub(crate) fn world_mut(&mut self) -> &mut World {
self.app.world_mut()
}
}
impl ApplicationHandler<WinitUserEvent> for WinitAppRunnerState {
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
if event_loop.exiting() {
return;
}
#[cfg(feature = "trace")]
let _span = tracing::info_span!("winit event_handler").entered();
if self.app.plugins_state() != PluginsState::Cleaned {
if self.app.plugins_state() != PluginsState::Ready {
#[cfg(not(target_arch = "wasm32"))]
tick_global_task_pools_on_main_thread();
} else {
self.app.finish();
self.app.cleanup();
}
self.redraw_requested = true;
}
self.wait_elapsed = match cause {
StartCause::WaitCancelled {
requested_resume: Some(resume),
..
} => {
resume <= Instant::now()
}
_ => true,
};
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.lifecycle = AppLifecycle::WillResume;
let mut create_window = SystemState::<CreateWindowParams>::from_world(self.world_mut());
create_windows(event_loop, create_window.get_mut(self.world_mut()));
create_window.apply(self.world_mut());
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: WinitUserEvent) {
self.user_event_received = true;
match event {
WinitUserEvent::WakeUp => {
self.redraw_requested = true;
}
WinitUserEvent::WindowAdded => {
let mut create_window =
SystemState::<CreateWindowParams>::from_world(self.world_mut());
create_windows(event_loop, create_window.get_mut(self.world_mut()));
create_window.apply(self.world_mut());
}
}
}
fn window_event(
&mut self,
_event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
self.window_event_received = true;
#[cfg_attr(
not(target_os = "windows"),
expect(unused_mut, reason = "only needs to be mut on windows for now")
)]
let mut manual_run_redraw_requested = false;
WINIT_WINDOWS.with_borrow(|winit_windows| {
ACCESS_KIT_ADAPTERS.with_borrow_mut(|access_kit_adapters| {
let (
mut window_resized,
mut window_backend_scale_factor_changed,
mut window_scale_factor_changed,
mut windows,
) = self
.message_writer_system_state
.get_mut(self.app.world_mut());
let Some(window) = winit_windows.get_window_entity(window_id) else {
warn!("Skipped event {event:?} for unknown winit Window Id {window_id:?}");
return;
};
let Ok((mut win, _, mut pressed_keys)) = windows.get_mut(window) else {
warn!(
"Window {window:?} is missing `Window` component, skipping event {event:?}"
);
return;
};
self.raw_winit_events.push(RawWinitWindowEvent {
window_id,
event: event.clone(),
});
if let Some(adapter) = access_kit_adapters.get_mut(&window)
&& let Some(winit_window) = winit_windows.get_window(window)
{
adapter.process_event(winit_window, &event);
}
match event {
WindowEvent::Resized(size) => {
react_to_resize(window, &mut win, size, &mut window_resized);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
react_to_scale_factor_change(
window,
&mut win,
scale_factor,
&mut window_backend_scale_factor_changed,
&mut window_scale_factor_changed,
);
}
WindowEvent::CloseRequested => self
.bevy_window_events
.send(WindowCloseRequested { window }),
WindowEvent::KeyboardInput {
ref event,
is_synthetic: false,
..
} => {
let keyboard_input = converters::convert_keyboard_input(event, window);
if event.state.is_pressed() {
pressed_keys.0.insert(
keyboard_input.key_code,
keyboard_input.logical_key.clone(),
);
} else {
pressed_keys.0.remove(&keyboard_input.key_code);
}
self.bevy_window_events.send(keyboard_input);
}
WindowEvent::CursorMoved { position, .. } => {
let physical_position = DVec2::new(position.x, position.y);
let last_position = win.physical_cursor_position();
let delta = last_position.map(|last_pos| {
(physical_position.as_vec2() - last_pos) / win.resolution.scale_factor()
});
win.set_physical_cursor_position(Some(physical_position));
let position =
(physical_position / win.resolution.scale_factor() as f64).as_vec2();
self.bevy_window_events.send(CursorMoved {
window,
position,
delta,
});
}
WindowEvent::CursorEntered { .. } => {
self.bevy_window_events.send(CursorEntered { window });
}
WindowEvent::CursorLeft { .. } => {
win.set_physical_cursor_position(None);
self.bevy_window_events.send(CursorLeft { window });
}
WindowEvent::MouseInput { state, button, .. } => {
self.bevy_window_events.send(MouseButtonInput {
button: converters::convert_mouse_button(button),
state: converters::convert_element_state(state),
window,
});
}
WindowEvent::PinchGesture { delta, .. } => {
self.bevy_window_events.send(PinchGesture(delta as f32));
}
WindowEvent::RotationGesture { delta, .. } => {
self.bevy_window_events.send(RotationGesture(delta));
}
WindowEvent::DoubleTapGesture { .. } => {
self.bevy_window_events.send(DoubleTapGesture);
}
WindowEvent::PanGesture { delta, .. } => {
self.bevy_window_events.send(PanGesture(Vec2 {
x: delta.x,
y: delta.y,
}));
}
WindowEvent::MouseWheel { delta, .. } => match delta {
event::MouseScrollDelta::LineDelta(x, y) => {
self.bevy_window_events.send(MouseWheel {
unit: MouseScrollUnit::Line,
x,
y,
window,
});
}
event::MouseScrollDelta::PixelDelta(p) => {
self.bevy_window_events.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: p.x as f32,
y: p.y as f32,
window,
});
}
},
WindowEvent::Touch(touch) => {
let location = touch
.location
.to_logical(win.resolution.scale_factor() as f64);
self.bevy_window_events
.send(converters::convert_touch_input(touch, location, window));
}
WindowEvent::Focused(focused) => {
win.focused = focused;
self.bevy_window_events
.send(WindowFocused { window, focused });
}
WindowEvent::Occluded(occluded) => {
self.bevy_window_events
.send(WindowOccluded { window, occluded });
}
WindowEvent::DroppedFile(path_buf) => {
self.bevy_window_events
.send(FileDragAndDrop::DroppedFile { window, path_buf });
}
WindowEvent::HoveredFile(path_buf) => {
self.bevy_window_events
.send(FileDragAndDrop::HoveredFile { window, path_buf });
}
WindowEvent::HoveredFileCancelled => {
self.bevy_window_events
.send(FileDragAndDrop::HoveredFileCanceled { window });
}
WindowEvent::Moved(position) => {
let position = ivec2(position.x, position.y);
win.position.set(position);
self.bevy_window_events
.send(WindowMoved { window, position });
}
WindowEvent::Ime(event) => match event {
event::Ime::Preedit(value, cursor) => {
self.bevy_window_events.send(Ime::Preedit {
window,
value,
cursor,
});
}
event::Ime::Commit(value) => {
self.bevy_window_events.send(Ime::Commit { window, value });
}
event::Ime::Enabled => {
self.bevy_window_events.send(Ime::Enabled { window });
}
event::Ime::Disabled => {
self.bevy_window_events.send(Ime::Disabled { window });
}
},
WindowEvent::ThemeChanged(theme) => {
self.bevy_window_events.send(WindowThemeChanged {
window,
theme: converters::convert_winit_theme(theme),
});
}
WindowEvent::Destroyed => {
self.bevy_window_events.send(WindowDestroyed { window });
}
WindowEvent::RedrawRequested => {
self.ran_update_since_last_redraw = false;
#[cfg(target_os = "windows")]
{
if self.startup_forced_updates == 0 {
manual_run_redraw_requested = true;
}
}
}
_ => {}
}
let mut windows = self.world_mut().query::<(&mut Window, &mut CachedWindow)>();
if let Ok((window_component, mut cache)) = windows.get_mut(self.world_mut(), window)
&& window_component.is_changed()
{
**cache = window_component.clone();
}
});
});
if manual_run_redraw_requested {
self.redraw_requested(_event_loop);
}
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
_device_id: DeviceId,
event: DeviceEvent,
) {
self.device_event_received = true;
if let DeviceEvent::MouseMotion { delta: (x, y) } = event {
let delta = Vec2::new(x as f32, y as f32);
self.bevy_window_events.send(MouseMotion { delta });
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
let mut create_monitor = SystemState::<CreateMonitorParams>::from_world(self.world_mut());
create_monitors(event_loop, create_monitor.get_mut(self.world_mut()));
create_monitor.apply(self.world_mut());
#[cfg(not(target_os = "windows"))]
self.redraw_requested(event_loop);
#[cfg(target_os = "windows")]
{
fn headless_or_all_invisible() -> bool {
WINIT_WINDOWS.with_borrow(|winit_windows| {
winit_windows
.windows
.iter()
.all(|(_, w)| !w.is_visible().unwrap_or(false))
})
}
if self.app_exit.is_none()
&& (self.startup_forced_updates > 0
|| matches!(self.update_mode, UpdateMode::Reactive { .. })
|| self.window_event_received
|| headless_or_all_invisible())
{
self.redraw_requested(event_loop);
}
}
}
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
self.lifecycle = AppLifecycle::WillSuspend;
}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
WINIT_WINDOWS.with(|ww| ww.borrow_mut().windows.clear());
let world = self.world_mut();
world.clear_all();
}
}
impl WinitAppRunnerState {
fn redraw_requested(&mut self, event_loop: &ActiveEventLoop) {
let mut redraw_message_cursor = MessageCursor::<RequestRedraw>::default();
let mut close_message_cursor = MessageCursor::<WindowCloseRequested>::default();
let mut focused_windows_state: SystemState<(Res<WinitSettings>, Query<(Entity, &Window)>)> =
SystemState::new(self.world_mut());
let (config, windows) = focused_windows_state.get(self.world());
let focused = windows.iter().any(|(_, window)| window.focused);
let mut update_mode = config.update_mode(focused);
let mut should_update = self.should_update(update_mode);
if self.startup_forced_updates > 0 {
self.startup_forced_updates -= 1;
should_update = true;
}
if self.lifecycle == AppLifecycle::WillSuspend {
self.lifecycle = AppLifecycle::Suspended;
should_update = true;
self.ran_update_since_last_redraw = false;
#[cfg(target_os = "android")]
{
let mut query = self
.world_mut()
.query_filtered::<Entity, With<PrimaryWindow>>();
let entity = query.single(&self.world()).unwrap();
self.world_mut()
.entity_mut(entity)
.remove::<RawHandleWrapper>();
}
}
if self.lifecycle == AppLifecycle::WillResume {
self.lifecycle = AppLifecycle::Running;
should_update = true;
self.redraw_requested = true;
#[cfg(target_os = "android")]
{
let mut query = self.world_mut()
.query_filtered::<(Entity, &Window, &CursorOptions), (With<CachedWindow>, Without<RawHandleWrapper>)>();
if let Ok((entity, window, cursor_options)) = query.single(&self.world()) {
let window = window.clone();
let cursor_options = cursor_options.clone();
WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
let mut create_window =
SystemState::<CreateWindowParams>::from_world(self.world_mut());
let (.., mut handlers, accessibility_requested, monitors) =
create_window.get_mut(self.world_mut());
let winit_window = winit_windows.create_window(
event_loop,
entity,
&window,
&cursor_options,
adapters,
&mut handlers,
&accessibility_requested,
&monitors,
);
let wrapper = RawHandleWrapper::new(winit_window).unwrap();
self.world_mut().entity_mut(entity).insert(wrapper);
});
});
}
}
}
if self.lifecycle != self.previous_lifecycle {
self.previous_lifecycle = self.lifecycle;
self.bevy_window_events.send(self.lifecycle);
}
let begin_frame_time = Instant::now();
if should_update {
let (_, windows) = focused_windows_state.get(self.world());
let all_invisible = windows.iter().all(|w| !w.1.visible);
if !self.ran_update_since_last_redraw || all_invisible {
self.run_app_update();
#[cfg(feature = "custom_cursor")]
self.update_cursors(event_loop);
#[cfg(not(feature = "custom_cursor"))]
self.update_cursors();
self.ran_update_since_last_redraw = true;
} else {
self.redraw_requested = true;
}
if let Some(app_redraw_events) = self.world().get_resource::<Messages<RequestRedraw>>()
&& redraw_message_cursor
.read(app_redraw_events)
.last()
.is_some()
{
self.redraw_requested = true;
}
if let Some(close_request_messages) = self
.world()
.get_resource::<Messages<WindowCloseRequested>>()
&& close_message_cursor
.read(close_request_messages)
.last()
.is_some()
{
self.redraw_requested = true;
}
let (config, windows) = focused_windows_state.get(self.world());
let focused = windows.iter().any(|(_, window)| window.focused);
update_mode = config.update_mode(focused);
}
if update_mode != self.update_mode {
self.redraw_requested = true;
self.wait_elapsed = true;
self.scheduled_tick_start = None;
self.update_mode = update_mode;
}
match update_mode {
UpdateMode::Continuous => {
cfg_if::cfg_if! {
if #[cfg(not(any(
target_arch = "wasm32",
target_os = "android",
target_os = "ios",
all(target_os = "linux", any(feature = "x11", feature = "wayland"))
)))]
{
let visible = WINIT_WINDOWS.with_borrow(|winit_windows| {
winit_windows.windows.iter().any(|(_, w)| {
w.is_visible().unwrap_or(false)
})
});
event_loop.set_control_flow(if visible {
ControlFlow::Wait
} else {
ControlFlow::Poll
});
}
else {
event_loop.set_control_flow(ControlFlow::Wait);
}
}
if let ControlFlow::Wait = event_loop.control_flow() {
self.redraw_requested = true;
}
}
UpdateMode::Reactive { wait, .. } => {
if self.wait_elapsed {
self.redraw_requested = true;
let begin_instant = self.scheduled_tick_start.unwrap_or(begin_frame_time);
if let Some(next) = begin_instant.checked_add(wait) {
let now = Instant::now();
if next < now {
event_loop.set_control_flow(ControlFlow::Poll);
self.scheduled_tick_start = Some(now);
} else {
event_loop.set_control_flow(ControlFlow::WaitUntil(next));
self.scheduled_tick_start = Some(next);
}
}
}
}
}
if self.redraw_requested && self.lifecycle != AppLifecycle::Suspended {
WINIT_WINDOWS.with_borrow(|winit_windows| {
for window in winit_windows.windows.values() {
window.request_redraw();
}
});
self.redraw_requested = false;
}
if let Some(app_exit) = self.app.should_exit() {
self.app_exit = Some(app_exit);
event_loop.exit();
}
}
fn should_update(&self, update_mode: UpdateMode) -> bool {
let handle_event = match update_mode {
UpdateMode::Continuous => {
self.wait_elapsed
|| self.user_event_received
|| self.window_event_received
|| self.device_event_received
}
UpdateMode::Reactive {
react_to_device_events,
react_to_user_events,
react_to_window_events,
..
} => {
self.wait_elapsed
|| (react_to_device_events && self.device_event_received)
|| (react_to_user_events && self.user_event_received)
|| (react_to_window_events && self.window_event_received)
}
};
handle_event && self.lifecycle.is_active()
}
fn run_app_update(&mut self) {
self.reset_on_update();
self.forward_bevy_events();
if self.app.plugins_state() == PluginsState::Cleaned {
self.app.update();
}
}
fn forward_bevy_events(&mut self) {
let raw_winit_events = self.raw_winit_events.drain(..).collect::<Vec<_>>();
let window_events = self.bevy_window_events.drain(..).collect::<Vec<_>>();
let world = self.world_mut();
if !raw_winit_events.is_empty() {
world
.resource_mut::<Messages<RawWinitWindowEvent>>()
.write_batch(raw_winit_events);
}
for window_event in window_events.iter() {
match window_event.clone() {
BevyWindowEvent::AppLifecycle(e) => {
world.write_message(e);
}
BevyWindowEvent::CursorEntered(e) => {
world.write_message(e);
}
BevyWindowEvent::CursorLeft(e) => {
world.write_message(e);
}
BevyWindowEvent::CursorMoved(e) => {
world.write_message(e);
}
BevyWindowEvent::FileDragAndDrop(e) => {
world.write_message(e);
}
BevyWindowEvent::Ime(e) => {
world.write_message(e);
}
BevyWindowEvent::RequestRedraw(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowBackendScaleFactorChanged(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowCloseRequested(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowCreated(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowDestroyed(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowFocused(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowMoved(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowOccluded(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowResized(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowScaleFactorChanged(e) => {
world.write_message(e);
}
BevyWindowEvent::WindowThemeChanged(e) => {
world.write_message(e);
}
BevyWindowEvent::MouseButtonInput(e) => {
world.write_message(e);
}
BevyWindowEvent::MouseMotion(e) => {
world.write_message(e);
}
BevyWindowEvent::MouseWheel(e) => {
world.write_message(e);
}
BevyWindowEvent::PinchGesture(e) => {
world.write_message(e);
}
BevyWindowEvent::RotationGesture(e) => {
world.write_message(e);
}
BevyWindowEvent::DoubleTapGesture(e) => {
world.write_message(e);
}
BevyWindowEvent::PanGesture(e) => {
world.write_message(e);
}
BevyWindowEvent::TouchInput(e) => {
world.write_message(e);
}
BevyWindowEvent::KeyboardInput(e) => {
world.write_message(e);
}
BevyWindowEvent::KeyboardFocusLost(e) => {
world.write_message(e);
}
}
}
if !window_events.is_empty() {
world
.resource_mut::<Messages<BevyWindowEvent>>()
.write_batch(window_events);
}
}
}
pub fn winit_runner(mut app: App, event_loop: EventLoop<WinitUserEvent>) -> AppExit {
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
}
let runner_state = WinitAppRunnerState::new(app);
trace!("starting winit event loop");
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
event_loop.spawn_app(runner_state);
AppExit::Success
} else {
let mut runner_state = runner_state;
if let Err(err) = event_loop.run_app(&mut runner_state) {
bevy_log::error!("winit event loop returned an error: {err}");
}
runner_state.app_exit.unwrap_or_else(|| {
bevy_log::error!("Failed to receive an app exit code! This is a bug");
AppExit::error()
})
}
}
}
pub(crate) fn react_to_resize(
window_entity: Entity,
window: &mut Window,
size: PhysicalSize<u32>,
window_resized: &mut MessageWriter<WindowResized>,
) {
window
.resolution
.set_physical_resolution(size.width, size.height);
window_resized.write(WindowResized {
window: window_entity,
width: window.width(),
height: window.height(),
});
}
pub(crate) fn react_to_scale_factor_change(
window_entity: Entity,
window: &mut Window,
scale_factor: f64,
window_backend_scale_factor_changed: &mut MessageWriter<WindowBackendScaleFactorChanged>,
window_scale_factor_changed: &mut MessageWriter<WindowScaleFactorChanged>,
) {
let prior_factor = window.resolution.scale_factor();
window.resolution.set_scale_factor(scale_factor as f32);
window_backend_scale_factor_changed.write(WindowBackendScaleFactorChanged {
window: window_entity,
scale_factor,
});
let scale_factor_override = window.resolution.scale_factor_override();
if scale_factor_override.is_none() && !relative_eq!(scale_factor as f32, prior_factor) {
window_scale_factor_changed.write(WindowScaleFactorChanged {
window: window_entity,
scale_factor,
});
}
}
#[cfg(test)]
mod tests {
use bevy_app::Update;
use super::*;
#[test]
fn test_react_to_scale_factor_change_with_changed_scale_factor() {
let (mut app, window_entity) = setup_react_to_scale_factor_change_test_app(1.0, 2.0);
app.update();
let window = app.world().get::<Window>(window_entity);
assert_eq!(window.unwrap().resolution.scale_factor(), 2.0);
let window_backend_scale_factor_changed_messages = app
.world()
.resource::<Messages<WindowBackendScaleFactorChanged>>();
assert_eq!(window_backend_scale_factor_changed_messages.len(), 1);
let mut window_backend_scale_factor_changed_messages_iter =
window_backend_scale_factor_changed_messages.iter_current_update_messages();
assert_eq!(
window_backend_scale_factor_changed_messages_iter.next(),
Some(&WindowBackendScaleFactorChanged {
window: window_entity,
scale_factor: 2.0,
})
);
assert_eq!(
window_backend_scale_factor_changed_messages_iter.next(),
None
);
let window_scale_factor_changed_messages =
app.world().resource::<Messages<WindowScaleFactorChanged>>();
assert_eq!(window_scale_factor_changed_messages.len(), 1);
let mut window_scale_factor_changed_messages_iter =
window_scale_factor_changed_messages.iter_current_update_messages();
assert_eq!(
window_scale_factor_changed_messages_iter.next(),
Some(&WindowScaleFactorChanged {
window: window_entity,
scale_factor: 2.0,
})
);
assert_eq!(window_scale_factor_changed_messages_iter.next(), None);
}
#[test]
fn test_react_to_scale_factor_change_with_same_scale_factor() {
let (mut app, window_entity) = setup_react_to_scale_factor_change_test_app(1.0, 1.0);
app.update();
let window = app.world().get::<Window>(window_entity);
assert_eq!(window.unwrap().resolution.scale_factor(), 1.0);
let window_backend_scale_factor_changed_messages = app
.world()
.resource::<Messages<WindowBackendScaleFactorChanged>>();
assert_eq!(window_backend_scale_factor_changed_messages.len(), 1);
let mut window_backend_scale_factor_changed_messages_iter =
window_backend_scale_factor_changed_messages.iter_current_update_messages();
assert_eq!(
window_backend_scale_factor_changed_messages_iter.next(),
Some(&WindowBackendScaleFactorChanged {
window: window_entity,
scale_factor: 1.0,
})
);
assert_eq!(
window_backend_scale_factor_changed_messages_iter.next(),
None
);
let window_scale_factor_changed_messages =
app.world().resource::<Messages<WindowScaleFactorChanged>>();
assert!(window_scale_factor_changed_messages.is_empty());
}
fn setup_react_to_scale_factor_change_test_app(
initial_scale_factor: f32,
changed_scale_factor: f64,
) -> (App, Entity) {
let mut app = App::new();
app.add_message::<WindowBackendScaleFactorChanged>();
app.add_message::<WindowScaleFactorChanged>();
app.add_systems(
Update,
move |mut window: Single<(Entity, &mut Window)>,
mut window_backend_scale_factor_changed: MessageWriter<
WindowBackendScaleFactorChanged,
>,
mut window_scale_factor_changed: MessageWriter<WindowScaleFactorChanged>| {
react_to_scale_factor_change(
window.0,
&mut window.1,
changed_scale_factor,
&mut window_backend_scale_factor_changed,
&mut window_scale_factor_changed,
);
},
);
let mut window = Window::default();
window.resolution.set_scale_factor(initial_scale_factor);
let window_entity = app.world_mut().spawn(window).id();
(app, window_entity)
}
}