Improve bevy_winit
documentation (#7609)
Redo of #7590 since I messed up my branch. # Objective - Revise docs. - Refactor event loop code a little bit to make it easier to follow. ## Solution - Do the above. --- ### Migration Guide - `UpdateMode::Reactive { max_wait: .. }` -> `UpdateMode::Reactive { wait: .. }` - `UpdateMode::ReactiveLowPower { max_wait: .. }` -> `UpdateMode::ReactiveLowPower { wait: .. }` --------- Co-authored-by: Sélène Amanita <134181069+Selene-Amanita@users.noreply.github.com>
This commit is contained in:
parent
da59de956f
commit
c0510c87ff
@ -16,17 +16,14 @@ mod winit_config;
|
|||||||
mod winit_windows;
|
mod winit_windows;
|
||||||
|
|
||||||
use bevy_a11y::AccessibilityRequested;
|
use bevy_a11y::AccessibilityRequested;
|
||||||
use bevy_ecs::system::{SystemParam, SystemState};
|
use system::{changed_windows, create_windows, despawn_windows, CachedWindow};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use bevy_tasks::tick_global_task_pools_on_main_thread;
|
|
||||||
use system::{changed_window, create_window, despawn_window, CachedWindow};
|
|
||||||
|
|
||||||
pub use winit_config::*;
|
pub use winit_config::*;
|
||||||
pub use winit_windows::*;
|
pub use winit_windows::*;
|
||||||
|
|
||||||
use bevy_app::{App, AppExit, Last, Plugin};
|
use bevy_app::{App, AppExit, Last, Plugin};
|
||||||
use bevy_ecs::event::{Events, ManualEventReader};
|
use bevy_ecs::event::{Events, ManualEventReader};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
|
use bevy_ecs::system::{SystemParam, SystemState};
|
||||||
use bevy_input::{
|
use bevy_input::{
|
||||||
keyboard::KeyboardInput,
|
keyboard::KeyboardInput,
|
||||||
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
|
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
|
||||||
@ -34,9 +31,11 @@ use bevy_input::{
|
|||||||
touchpad::{TouchpadMagnify, TouchpadRotate},
|
touchpad::{TouchpadMagnify, TouchpadRotate},
|
||||||
};
|
};
|
||||||
use bevy_math::{ivec2, DVec2, Vec2};
|
use bevy_math::{ivec2, DVec2, Vec2};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use bevy_tasks::tick_global_task_pools_on_main_thread;
|
||||||
use bevy_utils::{
|
use bevy_utils::{
|
||||||
tracing::{trace, warn},
|
tracing::{trace, warn},
|
||||||
Instant,
|
Duration, Instant,
|
||||||
};
|
};
|
||||||
use bevy_window::{
|
use bevy_window::{
|
||||||
exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime,
|
exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime,
|
||||||
@ -59,18 +58,23 @@ use crate::converters::convert_winit_theme;
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin};
|
use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin};
|
||||||
|
|
||||||
/// [`AndroidApp`] provides an interface to query the application state as well as monitor events (for example lifecycle and input events)
|
/// [`AndroidApp`] provides an interface to query the application state as well as monitor events
|
||||||
|
/// (for example lifecycle and input events).
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub static ANDROID_APP: std::sync::OnceLock<AndroidApp> = std::sync::OnceLock::new();
|
pub static ANDROID_APP: std::sync::OnceLock<AndroidApp> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
/// A [`Plugin`] that utilizes [`winit`] for window creation and event loop management.
|
/// A [`Plugin`] that uses [`winit`] to create and manage windows, and receive window and input
|
||||||
|
/// events.
|
||||||
|
///
|
||||||
|
/// This plugin will add systems and resources that sync with the [`winit`] backend and also
|
||||||
|
/// replace the exising [`App`] runner with one that constructs an [event loop](EventLoop) to
|
||||||
|
/// receive window and input events from the OS.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct WinitPlugin;
|
pub struct WinitPlugin;
|
||||||
|
|
||||||
impl Plugin for WinitPlugin {
|
impl Plugin for WinitPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
let mut event_loop_builder = EventLoopBuilder::<()>::with_user_event();
|
let mut event_loop_builder = EventLoopBuilder::<()>::with_user_event();
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
use winit::platform::android::EventLoopBuilderExtAndroid;
|
use winit::platform::android::EventLoopBuilderExtAndroid;
|
||||||
@ -82,21 +86,18 @@ impl Plugin for WinitPlugin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_loop = event_loop_builder.build();
|
|
||||||
app.insert_non_send_resource(event_loop);
|
|
||||||
|
|
||||||
app.init_non_send_resource::<WinitWindows>()
|
app.init_non_send_resource::<WinitWindows>()
|
||||||
.init_resource::<WinitSettings>()
|
.init_resource::<WinitSettings>()
|
||||||
.set_runner(winit_runner)
|
.set_runner(winit_runner)
|
||||||
// exit_on_all_closed only uses the query to determine if the query is empty,
|
|
||||||
// and so doesn't care about ordering relative to changed_window
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Last,
|
Last,
|
||||||
(
|
(
|
||||||
changed_window.ambiguous_with(exit_on_all_closed),
|
// `exit_on_all_closed` only checks if windows exist but doesn't access data,
|
||||||
// Update the state of the window before attempting to despawn to ensure consistent event ordering
|
// so we don't need to care about its ordering relative to `changed_windows`
|
||||||
despawn_window.after(changed_window),
|
changed_windows.ambiguous_with(exit_on_all_closed),
|
||||||
),
|
despawn_windows,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.add_plugins(AccessibilityPlugin);
|
app.add_plugins(AccessibilityPlugin);
|
||||||
@ -104,40 +105,46 @@ impl Plugin for WinitPlugin {
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
app.add_plugins(CanvasParentResizePlugin);
|
app.add_plugins(CanvasParentResizePlugin);
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
let event_loop = event_loop_builder.build();
|
||||||
let mut create_window_system_state: SystemState<(
|
|
||||||
Commands,
|
|
||||||
NonSendMut<EventLoop<()>>,
|
|
||||||
Query<(Entity, &mut Window)>,
|
|
||||||
EventWriter<WindowCreated>,
|
|
||||||
NonSendMut<WinitWindows>,
|
|
||||||
NonSendMut<AccessKitAdapters>,
|
|
||||||
ResMut<WinitActionHandlers>,
|
|
||||||
ResMut<AccessibilityRequested>,
|
|
||||||
)> = SystemState::from_world(&mut app.world);
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
// iOS, macOS, and Android don't like it if you create windows before the event loop is
|
||||||
let mut create_window_system_state: SystemState<(
|
// initialized.
|
||||||
Commands,
|
//
|
||||||
NonSendMut<EventLoop<()>>,
|
// See:
|
||||||
Query<(Entity, &mut Window)>,
|
// - https://github.com/rust-windowing/winit/blob/master/README.md#macos
|
||||||
EventWriter<WindowCreated>,
|
// - https://github.com/rust-windowing/winit/blob/master/README.md#ios
|
||||||
NonSendMut<WinitWindows>,
|
|
||||||
NonSendMut<AccessKitAdapters>,
|
|
||||||
ResMut<WinitActionHandlers>,
|
|
||||||
ResMut<AccessibilityRequested>,
|
|
||||||
ResMut<CanvasParentResizeEventChannel>,
|
|
||||||
)> = SystemState::from_world(&mut app.world);
|
|
||||||
|
|
||||||
// And for ios and macos, we should not create window early, all ui related code should be executed inside
|
|
||||||
// UIApplicationMain/NSApplicationMain.
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
|
||||||
{
|
{
|
||||||
|
// Otherwise, we want to create a window before `bevy_render` initializes the renderer
|
||||||
|
// so that we have a surface to use as a hint. This improves compatibility with `wgpu`
|
||||||
|
// backends, especially WASM/WebGL2.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let mut create_window_system_state: SystemState<(
|
||||||
|
Commands,
|
||||||
|
Query<(Entity, &mut Window)>,
|
||||||
|
EventWriter<WindowCreated>,
|
||||||
|
NonSendMut<WinitWindows>,
|
||||||
|
NonSendMut<AccessKitAdapters>,
|
||||||
|
ResMut<WinitActionHandlers>,
|
||||||
|
ResMut<AccessibilityRequested>,
|
||||||
|
)> = SystemState::from_world(&mut app.world);
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let mut create_window_system_state: SystemState<(
|
||||||
|
Commands,
|
||||||
|
Query<(Entity, &mut Window)>,
|
||||||
|
EventWriter<WindowCreated>,
|
||||||
|
NonSendMut<WinitWindows>,
|
||||||
|
NonSendMut<AccessKitAdapters>,
|
||||||
|
ResMut<WinitActionHandlers>,
|
||||||
|
ResMut<AccessibilityRequested>,
|
||||||
|
ResMut<CanvasParentResizeEventChannel>,
|
||||||
|
)> = SystemState::from_world(&mut app.world);
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let (
|
let (
|
||||||
commands,
|
commands,
|
||||||
event_loop,
|
mut windows,
|
||||||
mut new_windows,
|
|
||||||
event_writer,
|
event_writer,
|
||||||
winit_windows,
|
winit_windows,
|
||||||
adapters,
|
adapters,
|
||||||
@ -148,8 +155,7 @@ impl Plugin for WinitPlugin {
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
let (
|
let (
|
||||||
commands,
|
commands,
|
||||||
event_loop,
|
mut windows,
|
||||||
mut new_windows,
|
|
||||||
event_writer,
|
event_writer,
|
||||||
winit_windows,
|
winit_windows,
|
||||||
adapters,
|
adapters,
|
||||||
@ -158,13 +164,10 @@ impl Plugin for WinitPlugin {
|
|||||||
event_channel,
|
event_channel,
|
||||||
) = create_window_system_state.get_mut(&mut app.world);
|
) = create_window_system_state.get_mut(&mut app.world);
|
||||||
|
|
||||||
// Here we need to create a winit-window and give it a WindowHandle which the renderer can use.
|
create_windows(
|
||||||
// It needs to be spawned before the start of the startup schedule, so we cannot use a regular system.
|
|
||||||
// Instead we need to create the window and spawn it using direct world access
|
|
||||||
create_window(
|
|
||||||
commands,
|
|
||||||
&event_loop,
|
&event_loop,
|
||||||
new_windows.iter_mut(),
|
commands,
|
||||||
|
windows.iter_mut(),
|
||||||
event_writer,
|
event_writer,
|
||||||
winit_windows,
|
winit_windows,
|
||||||
adapters,
|
adapters,
|
||||||
@ -173,22 +176,23 @@ impl Plugin for WinitPlugin {
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
event_channel,
|
event_channel,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create_window_system_state.apply(&mut app.world);
|
||||||
}
|
}
|
||||||
|
|
||||||
create_window_system_state.apply(&mut app.world);
|
// `winit`'s windows are bound to the event loop that created them, so the event loop must
|
||||||
|
// be inserted as a resource here to pass it onto the runner.
|
||||||
|
app.insert_non_send_resource(event_loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<F>(event_loop: EventLoop<()>, event_handler: F) -> !
|
fn run<F, T>(event_loop: EventLoop<T>, event_handler: F) -> !
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
|
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||||
{
|
{
|
||||||
event_loop.run(event_handler)
|
event_loop.run(event_handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: It may be worth moving this cfg into a procedural macro so that it can be referenced by
|
|
||||||
// a single name instead of being copied around.
|
|
||||||
// https://gist.github.com/jakerr/231dee4a138f7a5f25148ea8f39b382e seems to work.
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "windows",
|
target_os = "windows",
|
||||||
target_os = "macos",
|
target_os = "macos",
|
||||||
@ -198,9 +202,9 @@ where
|
|||||||
target_os = "netbsd",
|
target_os = "netbsd",
|
||||||
target_os = "openbsd"
|
target_os = "openbsd"
|
||||||
))]
|
))]
|
||||||
fn run_return<F>(event_loop: &mut EventLoop<()>, event_handler: F)
|
fn run_return<F, T>(event_loop: &mut EventLoop<T>, event_handler: F)
|
||||||
where
|
where
|
||||||
F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
|
F: FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||||
{
|
{
|
||||||
use winit::platform::run_return::EventLoopExtRunReturn;
|
use winit::platform::run_return::EventLoopExtRunReturn;
|
||||||
event_loop.run_return(event_handler);
|
event_loop.run_return(event_handler);
|
||||||
@ -215,15 +219,16 @@ where
|
|||||||
target_os = "netbsd",
|
target_os = "netbsd",
|
||||||
target_os = "openbsd"
|
target_os = "openbsd"
|
||||||
)))]
|
)))]
|
||||||
fn run_return<F>(_event_loop: &mut EventLoop<()>, _event_handler: F)
|
fn run_return<F, T>(_event_loop: &mut EventLoop<T>, _event_handler: F)
|
||||||
where
|
where
|
||||||
F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
|
F: FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||||
{
|
{
|
||||||
panic!("Run return is not supported on this platform!")
|
panic!("Run return is not supported on this platform!")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SystemParam)]
|
#[derive(SystemParam)]
|
||||||
struct WindowEvents<'w> {
|
struct WindowAndInputEventWriters<'w> {
|
||||||
|
// `winit` `WindowEvent`s
|
||||||
window_resized: EventWriter<'w, WindowResized>,
|
window_resized: EventWriter<'w, WindowResized>,
|
||||||
window_close_requested: EventWriter<'w, WindowCloseRequested>,
|
window_close_requested: EventWriter<'w, WindowCloseRequested>,
|
||||||
window_scale_factor_changed: EventWriter<'w, WindowScaleFactorChanged>,
|
window_scale_factor_changed: EventWriter<'w, WindowScaleFactorChanged>,
|
||||||
@ -232,10 +237,6 @@ struct WindowEvents<'w> {
|
|||||||
window_moved: EventWriter<'w, WindowMoved>,
|
window_moved: EventWriter<'w, WindowMoved>,
|
||||||
window_theme_changed: EventWriter<'w, WindowThemeChanged>,
|
window_theme_changed: EventWriter<'w, WindowThemeChanged>,
|
||||||
window_destroyed: EventWriter<'w, WindowDestroyed>,
|
window_destroyed: EventWriter<'w, WindowDestroyed>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(SystemParam)]
|
|
||||||
struct InputEvents<'w> {
|
|
||||||
keyboard_input: EventWriter<'w, KeyboardInput>,
|
keyboard_input: EventWriter<'w, KeyboardInput>,
|
||||||
character_input: EventWriter<'w, ReceivedCharacter>,
|
character_input: EventWriter<'w, ReceivedCharacter>,
|
||||||
mouse_button_input: EventWriter<'w, MouseButtonInput>,
|
mouse_button_input: EventWriter<'w, MouseButtonInput>,
|
||||||
@ -244,74 +245,73 @@ struct InputEvents<'w> {
|
|||||||
mouse_wheel_input: EventWriter<'w, MouseWheel>,
|
mouse_wheel_input: EventWriter<'w, MouseWheel>,
|
||||||
touch_input: EventWriter<'w, TouchInput>,
|
touch_input: EventWriter<'w, TouchInput>,
|
||||||
ime_input: EventWriter<'w, Ime>,
|
ime_input: EventWriter<'w, Ime>,
|
||||||
}
|
file_drag_and_drop: EventWriter<'w, FileDragAndDrop>,
|
||||||
|
|
||||||
#[derive(SystemParam)]
|
|
||||||
struct CursorEvents<'w> {
|
|
||||||
cursor_moved: EventWriter<'w, CursorMoved>,
|
cursor_moved: EventWriter<'w, CursorMoved>,
|
||||||
cursor_entered: EventWriter<'w, CursorEntered>,
|
cursor_entered: EventWriter<'w, CursorEntered>,
|
||||||
cursor_left: EventWriter<'w, CursorLeft>,
|
cursor_left: EventWriter<'w, CursorLeft>,
|
||||||
|
// `winit` `DeviceEvent`s
|
||||||
|
mouse_motion: EventWriter<'w, MouseMotion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(any(
|
/// Persistent state that is used to run the [`App`] according to the current
|
||||||
// target_os = "linux",
|
/// [`UpdateMode`].
|
||||||
// target_os = "dragonfly",
|
struct WinitAppRunnerState {
|
||||||
// target_os = "freebsd",
|
/// Is `true` if the app is running and not suspended.
|
||||||
// target_os = "netbsd",
|
is_active: bool,
|
||||||
// target_os = "openbsd"
|
/// Is `true` if a new [`WindowEvent`] has been received since the last update.
|
||||||
// ))]
|
window_event_received: bool,
|
||||||
// pub fn winit_runner_any_thread(app: App) {
|
/// Is `true` if the app has requested a redraw since the last update.
|
||||||
// winit_runner_with(app, EventLoop::new_any_thread());
|
redraw_requested: bool,
|
||||||
// }
|
/// Is `true` if enough time has elapsed since `last_update` to run another update.
|
||||||
|
wait_elapsed: bool,
|
||||||
/// Stores state that must persist between frames.
|
/// The time the last update started.
|
||||||
struct WinitPersistentState {
|
|
||||||
/// Tracks whether or not the application is active or suspended.
|
|
||||||
active: bool,
|
|
||||||
/// Tracks whether or not an event has occurred this frame that would trigger an update in low
|
|
||||||
/// power mode. Should be reset at the end of every frame.
|
|
||||||
low_power_event: bool,
|
|
||||||
/// Tracks whether the event loop was started this frame because of a redraw request.
|
|
||||||
redraw_request_sent: bool,
|
|
||||||
/// Tracks if the event loop was started this frame because of a [`ControlFlow::WaitUntil`]
|
|
||||||
/// timeout.
|
|
||||||
timeout_reached: bool,
|
|
||||||
last_update: Instant,
|
last_update: Instant,
|
||||||
|
/// The time the next update is scheduled to start.
|
||||||
|
scheduled_update: Option<Instant>,
|
||||||
}
|
}
|
||||||
impl Default for WinitPersistentState {
|
|
||||||
|
impl Default for WinitAppRunnerState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
active: false,
|
is_active: false,
|
||||||
low_power_event: false,
|
window_event_received: false,
|
||||||
redraw_request_sent: false,
|
redraw_requested: false,
|
||||||
timeout_reached: false,
|
wait_elapsed: false,
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
|
scheduled_update: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default [`App::runner`] for the [`WinitPlugin`] plugin.
|
/// The default [`App::runner`] for the [`WinitPlugin`] plugin.
|
||||||
///
|
///
|
||||||
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the `EventLoop`.
|
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
|
||||||
|
/// `EventLoop`.
|
||||||
pub fn winit_runner(mut app: App) {
|
pub fn winit_runner(mut app: App) {
|
||||||
// We remove this so that we have ownership over it.
|
|
||||||
let mut event_loop = app
|
let mut event_loop = app
|
||||||
.world
|
.world
|
||||||
.remove_non_send_resource::<EventLoop<()>>()
|
.remove_non_send_resource::<EventLoop<()>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
let return_from_run = app.world.resource::<WinitSettings>().return_from_run;
|
||||||
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
|
||||||
let mut winit_state = WinitPersistentState::default();
|
|
||||||
app.world
|
app.world
|
||||||
.insert_non_send_resource(event_loop.create_proxy());
|
.insert_non_send_resource(event_loop.create_proxy());
|
||||||
|
|
||||||
let return_from_run = app.world.resource::<WinitSettings>().return_from_run;
|
let mut runner_state = WinitAppRunnerState::default();
|
||||||
|
|
||||||
trace!("Entering winit event loop");
|
// prepare structures to access data in the world
|
||||||
|
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||||
|
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
||||||
|
|
||||||
let mut focused_window_state: SystemState<(Res<WinitSettings>, Query<&Window>)> =
|
let mut focused_windows_state: SystemState<(Res<WinitSettings>, Query<&Window>)> =
|
||||||
SystemState::from_world(&mut app.world);
|
SystemState::new(&mut app.world);
|
||||||
|
|
||||||
|
let mut event_writer_system_state: SystemState<(
|
||||||
|
WindowAndInputEventWriters,
|
||||||
|
NonSend<WinitWindows>,
|
||||||
|
Query<(&mut Window, &mut CachedWindow)>,
|
||||||
|
)> = SystemState::new(&mut app.world);
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let mut create_window_system_state: SystemState<(
|
let mut create_window_system_state: SystemState<(
|
||||||
@ -338,6 +338,7 @@ pub fn winit_runner(mut app: App) {
|
|||||||
|
|
||||||
let mut finished_and_setup_done = false;
|
let mut finished_and_setup_done = false;
|
||||||
|
|
||||||
|
// setup up the event loop
|
||||||
let event_handler = move |event: Event<()>,
|
let event_handler = move |event: Event<()>,
|
||||||
event_loop: &EventLoopWindowTarget<()>,
|
event_loop: &EventLoopWindowTarget<()>,
|
||||||
control_flow: &mut ControlFlow| {
|
control_flow: &mut ControlFlow| {
|
||||||
@ -353,77 +354,82 @@ pub fn winit_runner(mut app: App) {
|
|||||||
app.cleanup();
|
app.cleanup();
|
||||||
finished_and_setup_done = true;
|
finished_and_setup_done = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
|
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
|
||||||
if app_exit_event_reader.iter(app_exit_events).last().is_some() {
|
if app_exit_event_reader.iter(app_exit_events).last().is_some() {
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
event::Event::NewEvents(start) => {
|
event::Event::NewEvents(start_cause) => match start_cause {
|
||||||
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
|
StartCause::Init => {
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))]
|
||||||
|
{
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let (
|
||||||
|
commands,
|
||||||
|
mut windows,
|
||||||
|
event_writer,
|
||||||
|
winit_windows,
|
||||||
|
adapters,
|
||||||
|
handlers,
|
||||||
|
accessibility_requested,
|
||||||
|
) = create_window_system_state.get_mut(&mut app.world);
|
||||||
|
|
||||||
let app_focused = window_focused_query.iter().any(|window| window.focused);
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let (
|
||||||
|
commands,
|
||||||
|
mut windows,
|
||||||
|
event_writer,
|
||||||
|
winit_windows,
|
||||||
|
adapters,
|
||||||
|
handlers,
|
||||||
|
accessibility_requested,
|
||||||
|
event_channel,
|
||||||
|
) = create_window_system_state.get_mut(&mut app.world);
|
||||||
|
|
||||||
// Check if either the `WaitUntil` timeout was triggered by winit, or that same
|
create_windows(
|
||||||
// amount of time has elapsed since the last app update. This manual check is needed
|
event_loop,
|
||||||
// because we don't know if the criteria for an app update were met until the end of
|
commands,
|
||||||
// the frame.
|
windows.iter_mut(),
|
||||||
let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. });
|
event_writer,
|
||||||
let now = Instant::now();
|
winit_windows,
|
||||||
let manual_timeout_reached = match winit_config.update_mode(app_focused) {
|
adapters,
|
||||||
UpdateMode::Continuous => false,
|
handlers,
|
||||||
UpdateMode::Reactive { max_wait }
|
accessibility_requested,
|
||||||
| UpdateMode::ReactiveLowPower { max_wait } => {
|
#[cfg(target_arch = "wasm32")]
|
||||||
now.duration_since(winit_state.last_update) >= *max_wait
|
event_channel,
|
||||||
|
);
|
||||||
|
|
||||||
|
create_window_system_state.apply(&mut app.world);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
// The low_power_event state and timeout must be reset at the start of every frame.
|
_ => {
|
||||||
winit_state.low_power_event = false;
|
if let Some(t) = runner_state.scheduled_update {
|
||||||
winit_state.timeout_reached = auto_timeout_reached || manual_timeout_reached;
|
let now = Instant::now();
|
||||||
}
|
let remaining = t.checked_duration_since(now).unwrap_or(Duration::ZERO);
|
||||||
|
runner_state.wait_elapsed = remaining.is_zero();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
event::Event::WindowEvent {
|
event::Event::WindowEvent {
|
||||||
event,
|
event, window_id, ..
|
||||||
window_id: winit_window_id,
|
|
||||||
..
|
|
||||||
} => {
|
} => {
|
||||||
// Fetch and prepare details from the world
|
let (mut event_writers, winit_windows, mut windows) =
|
||||||
let mut system_state: SystemState<(
|
event_writer_system_state.get_mut(&mut app.world);
|
||||||
NonSend<WinitWindows>,
|
|
||||||
Query<(&mut Window, &mut CachedWindow)>,
|
|
||||||
WindowEvents,
|
|
||||||
InputEvents,
|
|
||||||
CursorEvents,
|
|
||||||
EventWriter<FileDragAndDrop>,
|
|
||||||
)> = SystemState::new(&mut app.world);
|
|
||||||
let (
|
|
||||||
winit_windows,
|
|
||||||
mut window_query,
|
|
||||||
mut window_events,
|
|
||||||
mut input_events,
|
|
||||||
mut cursor_events,
|
|
||||||
mut file_drag_and_drop_events,
|
|
||||||
) = system_state.get_mut(&mut app.world);
|
|
||||||
|
|
||||||
// Entity of this window
|
let Some(window_entity) = winit_windows.get_window_entity(window_id) else {
|
||||||
let window_entity =
|
|
||||||
if let Some(entity) = winit_windows.get_window_entity(winit_window_id) {
|
|
||||||
entity
|
|
||||||
} else {
|
|
||||||
warn!(
|
warn!(
|
||||||
"Skipped event {:?} for unknown winit Window Id {:?}",
|
"Skipped event {:?} for unknown winit Window Id {:?}",
|
||||||
event, winit_window_id
|
event, window_id
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut window, mut cache) =
|
let Ok((mut window, mut cache)) = windows.get_mut(window_entity) else {
|
||||||
if let Ok((window, info)) = window_query.get_mut(window_entity) {
|
|
||||||
(window, info)
|
|
||||||
} else {
|
|
||||||
warn!(
|
warn!(
|
||||||
"Window {:?} is missing `Window` component, skipping event {:?}",
|
"Window {:?} is missing `Window` component, skipping event {:?}",
|
||||||
window_entity, event
|
window_entity, event
|
||||||
@ -431,7 +437,7 @@ pub fn winit_runner(mut app: App) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
winit_state.low_power_event = true;
|
runner_state.window_event_received = true;
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::Resized(size) => {
|
WindowEvent::Resized(size) => {
|
||||||
@ -439,67 +445,64 @@ pub fn winit_runner(mut app: App) {
|
|||||||
.resolution
|
.resolution
|
||||||
.set_physical_resolution(size.width, size.height);
|
.set_physical_resolution(size.width, size.height);
|
||||||
|
|
||||||
window_events.window_resized.send(WindowResized {
|
event_writers.window_resized.send(WindowResized {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
width: window.width(),
|
width: window.width(),
|
||||||
height: window.height(),
|
height: window.height(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::CloseRequested => {
|
WindowEvent::CloseRequested => {
|
||||||
window_events
|
event_writers
|
||||||
.window_close_requested
|
.window_close_requested
|
||||||
.send(WindowCloseRequested {
|
.send(WindowCloseRequested {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput { ref input, .. } => {
|
WindowEvent::KeyboardInput { ref input, .. } => {
|
||||||
input_events
|
event_writers
|
||||||
.keyboard_input
|
.keyboard_input
|
||||||
.send(converters::convert_keyboard_input(input, window_entity));
|
.send(converters::convert_keyboard_input(input, window_entity));
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
let physical_position = DVec2::new(position.x, position.y);
|
let physical_position = DVec2::new(position.x, position.y);
|
||||||
|
|
||||||
window.set_physical_cursor_position(Some(physical_position));
|
window.set_physical_cursor_position(Some(physical_position));
|
||||||
|
event_writers.cursor_moved.send(CursorMoved {
|
||||||
cursor_events.cursor_moved.send(CursorMoved {
|
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
position: (physical_position / window.resolution.scale_factor())
|
position: (physical_position / window.resolution.scale_factor())
|
||||||
.as_vec2(),
|
.as_vec2(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::CursorEntered { .. } => {
|
WindowEvent::CursorEntered { .. } => {
|
||||||
cursor_events.cursor_entered.send(CursorEntered {
|
event_writers.cursor_entered.send(CursorEntered {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::CursorLeft { .. } => {
|
WindowEvent::CursorLeft { .. } => {
|
||||||
window.set_physical_cursor_position(None);
|
window.set_physical_cursor_position(None);
|
||||||
|
event_writers.cursor_left.send(CursorLeft {
|
||||||
cursor_events.cursor_left.send(CursorLeft {
|
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
input_events.mouse_button_input.send(MouseButtonInput {
|
event_writers.mouse_button_input.send(MouseButtonInput {
|
||||||
button: converters::convert_mouse_button(button),
|
button: converters::convert_mouse_button(button),
|
||||||
state: converters::convert_element_state(state),
|
state: converters::convert_element_state(state),
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::TouchpadMagnify { delta, .. } => {
|
WindowEvent::TouchpadMagnify { delta, .. } => {
|
||||||
input_events
|
event_writers
|
||||||
.touchpad_magnify_input
|
.touchpad_magnify_input
|
||||||
.send(TouchpadMagnify(delta as f32));
|
.send(TouchpadMagnify(delta as f32));
|
||||||
}
|
}
|
||||||
WindowEvent::TouchpadRotate { delta, .. } => {
|
WindowEvent::TouchpadRotate { delta, .. } => {
|
||||||
input_events
|
event_writers
|
||||||
.touchpad_rotate_input
|
.touchpad_rotate_input
|
||||||
.send(TouchpadRotate(delta));
|
.send(TouchpadRotate(delta));
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||||
event::MouseScrollDelta::LineDelta(x, y) => {
|
event::MouseScrollDelta::LineDelta(x, y) => {
|
||||||
input_events.mouse_wheel_input.send(MouseWheel {
|
event_writers.mouse_wheel_input.send(MouseWheel {
|
||||||
unit: MouseScrollUnit::Line,
|
unit: MouseScrollUnit::Line,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
@ -507,7 +510,7 @@ pub fn winit_runner(mut app: App) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
event::MouseScrollDelta::PixelDelta(p) => {
|
event::MouseScrollDelta::PixelDelta(p) => {
|
||||||
input_events.mouse_wheel_input.send(MouseWheel {
|
event_writers.mouse_wheel_input.send(MouseWheel {
|
||||||
unit: MouseScrollUnit::Pixel,
|
unit: MouseScrollUnit::Pixel,
|
||||||
x: p.x as f32,
|
x: p.x as f32,
|
||||||
y: p.y as f32,
|
y: p.y as f32,
|
||||||
@ -517,23 +520,21 @@ pub fn winit_runner(mut app: App) {
|
|||||||
},
|
},
|
||||||
WindowEvent::Touch(touch) => {
|
WindowEvent::Touch(touch) => {
|
||||||
let location = touch.location.to_logical(window.resolution.scale_factor());
|
let location = touch.location.to_logical(window.resolution.scale_factor());
|
||||||
|
event_writers
|
||||||
// Event
|
|
||||||
input_events
|
|
||||||
.touch_input
|
.touch_input
|
||||||
.send(converters::convert_touch_input(touch, location));
|
.send(converters::convert_touch_input(touch, location));
|
||||||
}
|
}
|
||||||
WindowEvent::ReceivedCharacter(c) => {
|
WindowEvent::ReceivedCharacter(char) => {
|
||||||
input_events.character_input.send(ReceivedCharacter {
|
event_writers.character_input.send(ReceivedCharacter {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
char: c,
|
char,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::ScaleFactorChanged {
|
WindowEvent::ScaleFactorChanged {
|
||||||
scale_factor,
|
scale_factor,
|
||||||
new_inner_size,
|
new_inner_size,
|
||||||
} => {
|
} => {
|
||||||
window_events.window_backend_scale_factor_changed.send(
|
event_writers.window_backend_scale_factor_changed.send(
|
||||||
WindowBackendScaleFactorChanged {
|
WindowBackendScaleFactorChanged {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
@ -545,17 +546,14 @@ pub fn winit_runner(mut app: App) {
|
|||||||
let new_factor = window.resolution.scale_factor();
|
let new_factor = window.resolution.scale_factor();
|
||||||
|
|
||||||
if let Some(forced_factor) = window.resolution.scale_factor_override() {
|
if let Some(forced_factor) = window.resolution.scale_factor_override() {
|
||||||
// If there is a scale factor override, then force that to be used
|
// This window is overriding the OS-suggested DPI, so its physical size
|
||||||
// Otherwise, use the OS suggested size
|
// should be set based on the overriding value. Its logical size already
|
||||||
// We have already told the OS about our resize constraints, so
|
// incorporates any resize constraints.
|
||||||
// the new_inner_size should take those into account
|
|
||||||
*new_inner_size =
|
*new_inner_size =
|
||||||
winit::dpi::LogicalSize::new(window.width(), window.height())
|
winit::dpi::LogicalSize::new(window.width(), window.height())
|
||||||
.to_physical::<u32>(forced_factor);
|
.to_physical::<u32>(forced_factor);
|
||||||
// TODO: Should this not trigger a WindowsScaleFactorChanged?
|
|
||||||
} else if approx::relative_ne!(new_factor, prior_factor) {
|
} else if approx::relative_ne!(new_factor, prior_factor) {
|
||||||
// Trigger a change event if they are approximately different
|
event_writers.window_scale_factor_changed.send(
|
||||||
window_events.window_scale_factor_changed.send(
|
|
||||||
WindowScaleFactorChanged {
|
WindowScaleFactorChanged {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
@ -568,7 +566,7 @@ pub fn winit_runner(mut app: App) {
|
|||||||
if approx::relative_ne!(window.width(), new_logical_width)
|
if approx::relative_ne!(window.width(), new_logical_width)
|
||||||
|| approx::relative_ne!(window.height(), new_logical_height)
|
|| approx::relative_ne!(window.height(), new_logical_height)
|
||||||
{
|
{
|
||||||
window_events.window_resized.send(WindowResized {
|
event_writers.window_resized.send(WindowResized {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
width: new_logical_width,
|
width: new_logical_width,
|
||||||
height: new_logical_height,
|
height: new_logical_height,
|
||||||
@ -579,68 +577,70 @@ pub fn winit_runner(mut app: App) {
|
|||||||
.set_physical_resolution(new_inner_size.width, new_inner_size.height);
|
.set_physical_resolution(new_inner_size.width, new_inner_size.height);
|
||||||
}
|
}
|
||||||
WindowEvent::Focused(focused) => {
|
WindowEvent::Focused(focused) => {
|
||||||
// Component
|
|
||||||
window.focused = focused;
|
window.focused = focused;
|
||||||
|
event_writers.window_focused.send(WindowFocused {
|
||||||
window_events.window_focused.send(WindowFocused {
|
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
focused,
|
focused,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::DroppedFile(path_buf) => {
|
WindowEvent::DroppedFile(path_buf) => {
|
||||||
file_drag_and_drop_events.send(FileDragAndDrop::DroppedFile {
|
event_writers
|
||||||
window: window_entity,
|
.file_drag_and_drop
|
||||||
path_buf,
|
.send(FileDragAndDrop::DroppedFile {
|
||||||
});
|
window: window_entity,
|
||||||
|
path_buf,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::HoveredFile(path_buf) => {
|
WindowEvent::HoveredFile(path_buf) => {
|
||||||
file_drag_and_drop_events.send(FileDragAndDrop::HoveredFile {
|
event_writers
|
||||||
window: window_entity,
|
.file_drag_and_drop
|
||||||
path_buf,
|
.send(FileDragAndDrop::HoveredFile {
|
||||||
});
|
window: window_entity,
|
||||||
|
path_buf,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::HoveredFileCancelled => {
|
WindowEvent::HoveredFileCancelled => {
|
||||||
file_drag_and_drop_events.send(FileDragAndDrop::HoveredFileCanceled {
|
event_writers.file_drag_and_drop.send(
|
||||||
window: window_entity,
|
FileDragAndDrop::HoveredFileCanceled {
|
||||||
});
|
window: window_entity,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
WindowEvent::Moved(position) => {
|
WindowEvent::Moved(position) => {
|
||||||
let position = ivec2(position.x, position.y);
|
let position = ivec2(position.x, position.y);
|
||||||
|
|
||||||
window.position.set(position);
|
window.position.set(position);
|
||||||
|
event_writers.window_moved.send(WindowMoved {
|
||||||
window_events.window_moved.send(WindowMoved {
|
|
||||||
entity: window_entity,
|
entity: window_entity,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::Ime(event) => match event {
|
WindowEvent::Ime(event) => match event {
|
||||||
event::Ime::Preedit(value, cursor) => {
|
event::Ime::Preedit(value, cursor) => {
|
||||||
input_events.ime_input.send(Ime::Preedit {
|
event_writers.ime_input.send(Ime::Preedit {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
value,
|
value,
|
||||||
cursor,
|
cursor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
event::Ime::Commit(value) => input_events.ime_input.send(Ime::Commit {
|
event::Ime::Commit(value) => event_writers.ime_input.send(Ime::Commit {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
value,
|
value,
|
||||||
}),
|
}),
|
||||||
event::Ime::Enabled => input_events.ime_input.send(Ime::Enabled {
|
event::Ime::Enabled => event_writers.ime_input.send(Ime::Enabled {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
}),
|
}),
|
||||||
event::Ime::Disabled => input_events.ime_input.send(Ime::Disabled {
|
event::Ime::Disabled => event_writers.ime_input.send(Ime::Disabled {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
WindowEvent::ThemeChanged(theme) => {
|
WindowEvent::ThemeChanged(theme) => {
|
||||||
window_events.window_theme_changed.send(WindowThemeChanged {
|
event_writers.window_theme_changed.send(WindowThemeChanged {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
theme: convert_winit_theme(theme),
|
theme: convert_winit_theme(theme),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::Destroyed => {
|
WindowEvent::Destroyed => {
|
||||||
window_events.window_destroyed.send(WindowDestroyed {
|
event_writers.window_destroyed.send(WindowDestroyed {
|
||||||
window: window_entity,
|
window: window_entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -655,132 +655,133 @@ pub fn winit_runner(mut app: App) {
|
|||||||
event: DeviceEvent::MouseMotion { delta: (x, y) },
|
event: DeviceEvent::MouseMotion { delta: (x, y) },
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let mut system_state: SystemState<EventWriter<MouseMotion>> =
|
let (mut event_writers, _, _) = event_writer_system_state.get_mut(&mut app.world);
|
||||||
SystemState::new(&mut app.world);
|
event_writers.mouse_motion.send(MouseMotion {
|
||||||
let mut mouse_motion = system_state.get_mut(&mut app.world);
|
|
||||||
|
|
||||||
mouse_motion.send(MouseMotion {
|
|
||||||
delta: Vec2::new(x as f32, y as f32),
|
delta: Vec2::new(x as f32, y as f32),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
event::Event::Suspended => {
|
event::Event::Suspended => {
|
||||||
winit_state.active = false;
|
runner_state.is_active = false;
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
// Bevy doesn't support suspend/resume so we just exit
|
// Android sending this event invalidates all render surfaces.
|
||||||
// and Android will restart the application on resume
|
// TODO
|
||||||
// TODO: Save save some state and load on resume
|
// Upon resume, check if the new render surfaces are compatible with the
|
||||||
|
// existing render device. If not (which should basically never happen),
|
||||||
|
// then try to rebuild the renderer.
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event::Event::Resumed => {
|
event::Event::Resumed => {
|
||||||
winit_state.active = true;
|
runner_state.is_active = true;
|
||||||
}
|
}
|
||||||
event::Event::MainEventsCleared => {
|
event::Event::MainEventsCleared => {
|
||||||
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
|
if runner_state.is_active {
|
||||||
|
let (config, windows) = focused_windows_state.get(&app.world);
|
||||||
let update = if winit_state.active {
|
let focused = windows.iter().any(|window| window.focused);
|
||||||
// True if _any_ windows are currently being focused
|
let should_update = match config.update_mode(focused) {
|
||||||
let app_focused = window_focused_query.iter().any(|window| window.focused);
|
UpdateMode::Continuous | UpdateMode::Reactive { .. } => {
|
||||||
match winit_config.update_mode(app_focused) {
|
// `Reactive`: In order for `event_handler` to have been called, either
|
||||||
UpdateMode::Continuous | UpdateMode::Reactive { .. } => true,
|
// we received a window or raw input event, the `wait` elapsed, or a
|
||||||
UpdateMode::ReactiveLowPower { .. } => {
|
// redraw was requested (by the app or the OS). There are no other
|
||||||
winit_state.low_power_event
|
// conditions, so we can just return `true` here.
|
||||||
|| winit_state.redraw_request_sent
|
true
|
||||||
|| winit_state.timeout_reached
|
|
||||||
}
|
}
|
||||||
}
|
UpdateMode::ReactiveLowPower { .. } => {
|
||||||
} else {
|
runner_state.wait_elapsed
|
||||||
false
|
|| runner_state.redraw_requested
|
||||||
};
|
|| runner_state.window_event_received
|
||||||
|
|
||||||
if update && finished_and_setup_done {
|
|
||||||
winit_state.last_update = Instant::now();
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::RedrawEventsCleared => {
|
|
||||||
{
|
|
||||||
// Fetch from world
|
|
||||||
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
|
|
||||||
|
|
||||||
// True if _any_ windows are currently being focused
|
|
||||||
let app_focused = window_focused_query.iter().any(|window| window.focused);
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
use UpdateMode::*;
|
|
||||||
*control_flow = match winit_config.update_mode(app_focused) {
|
|
||||||
Continuous => ControlFlow::Poll,
|
|
||||||
Reactive { max_wait } | ReactiveLowPower { max_wait } => {
|
|
||||||
if let Some(instant) = now.checked_add(*max_wait) {
|
|
||||||
ControlFlow::WaitUntil(instant)
|
|
||||||
} else {
|
|
||||||
ControlFlow::Wait
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// This block needs to run after `app.update()` in `MainEventsCleared`. Otherwise,
|
if finished_and_setup_done && should_update {
|
||||||
// we won't be able to see redraw requests until the next event, defeating the
|
// reset these on each update
|
||||||
// purpose of a redraw request!
|
runner_state.wait_elapsed = false;
|
||||||
let mut redraw = false;
|
runner_state.window_event_received = false;
|
||||||
if let Some(app_redraw_events) = app.world.get_resource::<Events<RequestRedraw>>() {
|
runner_state.redraw_requested = false;
|
||||||
if redraw_event_reader.iter(app_redraw_events).last().is_some() {
|
runner_state.last_update = Instant::now();
|
||||||
*control_flow = ControlFlow::Poll;
|
|
||||||
redraw = true;
|
app.update();
|
||||||
|
|
||||||
|
// decide when to run the next update
|
||||||
|
let (config, windows) = focused_windows_state.get(&app.world);
|
||||||
|
let focused = windows.iter().any(|window| window.focused);
|
||||||
|
match config.update_mode(focused) {
|
||||||
|
UpdateMode::Continuous => *control_flow = ControlFlow::Poll,
|
||||||
|
UpdateMode::Reactive { wait }
|
||||||
|
| UpdateMode::ReactiveLowPower { wait } => {
|
||||||
|
if let Some(next) = runner_state.last_update.checked_add(*wait) {
|
||||||
|
runner_state.scheduled_update = Some(next);
|
||||||
|
*control_flow = ControlFlow::WaitUntil(next);
|
||||||
|
} else {
|
||||||
|
runner_state.scheduled_update = None;
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(app_redraw_events) =
|
||||||
|
app.world.get_resource::<Events<RequestRedraw>>()
|
||||||
|
{
|
||||||
|
if redraw_event_reader.iter(app_redraw_events).last().is_some() {
|
||||||
|
runner_state.redraw_requested = true;
|
||||||
|
*control_flow = ControlFlow::Poll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
|
||||||
|
if app_exit_event_reader.iter(app_exit_events).last().is_some() {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create any new windows
|
||||||
|
// (even if app did not update, some may have been created by plugin setup)
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let (
|
||||||
|
commands,
|
||||||
|
mut windows,
|
||||||
|
event_writer,
|
||||||
|
winit_windows,
|
||||||
|
adapters,
|
||||||
|
handlers,
|
||||||
|
accessibility_requested,
|
||||||
|
) = create_window_system_state.get_mut(&mut app.world);
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let (
|
||||||
|
commands,
|
||||||
|
mut windows,
|
||||||
|
event_writer,
|
||||||
|
winit_windows,
|
||||||
|
adapters,
|
||||||
|
handlers,
|
||||||
|
accessibility_requested,
|
||||||
|
event_channel,
|
||||||
|
) = create_window_system_state.get_mut(&mut app.world);
|
||||||
|
|
||||||
|
create_windows(
|
||||||
|
event_loop,
|
||||||
|
commands,
|
||||||
|
windows.iter_mut(),
|
||||||
|
event_writer,
|
||||||
|
winit_windows,
|
||||||
|
adapters,
|
||||||
|
handlers,
|
||||||
|
accessibility_requested,
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
event_channel,
|
||||||
|
);
|
||||||
|
|
||||||
|
create_window_system_state.apply(&mut app.world);
|
||||||
}
|
}
|
||||||
|
|
||||||
winit_state.redraw_request_sent = redraw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if winit_state.active {
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
let (
|
|
||||||
commands,
|
|
||||||
mut new_windows,
|
|
||||||
created_window_writer,
|
|
||||||
winit_windows,
|
|
||||||
adapters,
|
|
||||||
handlers,
|
|
||||||
accessibility_requested,
|
|
||||||
) = create_window_system_state.get_mut(&mut app.world);
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
let (
|
|
||||||
commands,
|
|
||||||
mut new_windows,
|
|
||||||
created_window_writer,
|
|
||||||
winit_windows,
|
|
||||||
adapters,
|
|
||||||
handlers,
|
|
||||||
accessibility_requested,
|
|
||||||
canvas_parent_resize_channel,
|
|
||||||
) = create_window_system_state.get_mut(&mut app.world);
|
|
||||||
|
|
||||||
// Responsible for creating new windows
|
|
||||||
create_window(
|
|
||||||
commands,
|
|
||||||
event_loop,
|
|
||||||
new_windows.iter_mut(),
|
|
||||||
created_window_writer,
|
|
||||||
winit_windows,
|
|
||||||
adapters,
|
|
||||||
handlers,
|
|
||||||
accessibility_requested,
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
canvas_parent_resize_channel,
|
|
||||||
);
|
|
||||||
|
|
||||||
create_window_system_state.apply(&mut app.world);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// If true, returns control from Winit back to the main Bevy loop
|
trace!("starting winit event loop");
|
||||||
if return_from_run {
|
if return_from_run {
|
||||||
run_return(&mut event_loop, event_handler);
|
run_return(&mut event_loop, event_handler);
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,14 +30,15 @@ use crate::{
|
|||||||
get_best_videomode, get_fitting_videomode, WinitWindows,
|
get_best_videomode, get_fitting_videomode, WinitWindows,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// System responsible for creating new windows whenever a [`Window`] component is added
|
/// Creates new windows on the [`winit`] backend for each entity with a newly-added
|
||||||
/// to an entity.
|
/// [`Window`] component.
|
||||||
///
|
///
|
||||||
/// This will default any necessary components if they are not already added.
|
/// If any of these entities are missing required components, those will be added with their
|
||||||
|
/// default values.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn create_window<'a>(
|
pub(crate) fn create_windows<'a>(
|
||||||
mut commands: Commands,
|
|
||||||
event_loop: &EventLoopWindowTarget<()>,
|
event_loop: &EventLoopWindowTarget<()>,
|
||||||
|
mut commands: Commands,
|
||||||
created_windows: impl Iterator<Item = (Entity, Mut<'a, Window>)>,
|
created_windows: impl Iterator<Item = (Entity, Mut<'a, Window>)>,
|
||||||
mut event_writer: EventWriter<WindowCreated>,
|
mut event_writer: EventWriter<WindowCreated>,
|
||||||
mut winit_windows: NonSendMut<WinitWindows>,
|
mut winit_windows: NonSendMut<WinitWindows>,
|
||||||
@ -103,7 +104,7 @@ pub(crate) fn create_window<'a>(
|
|||||||
#[derive(Debug, Clone, Resource)]
|
#[derive(Debug, Clone, Resource)]
|
||||||
pub struct WindowTitleCache(HashMap<Entity, String>);
|
pub struct WindowTitleCache(HashMap<Entity, String>);
|
||||||
|
|
||||||
pub(crate) fn despawn_window(
|
pub(crate) fn despawn_windows(
|
||||||
mut closed: RemovedComponents<Window>,
|
mut closed: RemovedComponents<Window>,
|
||||||
window_entities: Query<&Window>,
|
window_entities: Query<&Window>,
|
||||||
mut close_events: EventWriter<WindowClosed>,
|
mut close_events: EventWriter<WindowClosed>,
|
||||||
@ -126,14 +127,15 @@ pub struct CachedWindow {
|
|||||||
pub window: Window,
|
pub window: Window,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect changes to the window and update the winit window accordingly.
|
/// Propagates changes from [`Window`] entities to the [`winit`] backend.
|
||||||
//
|
///
|
||||||
// Notes:
|
/// # Notes
|
||||||
// - [`Window::present_mode`] and [`Window::composite_alpha_mode`] updating should be handled in the bevy render crate.
|
///
|
||||||
// - [`Window::transparent`] currently cannot be updated after startup for winit.
|
/// - [`Window::present_mode`] and [`Window::composite_alpha_mode`] changes are handled by the `bevy_render` crate.
|
||||||
// - [`Window::canvas`] currently cannot be updated after startup, not entirely sure if it would work well with the
|
/// - [`Window::transparent`] cannot be changed after the window is created.
|
||||||
// event channel stuff.
|
/// - [`Window::canvas`] cannot be changed after the window is created.
|
||||||
pub(crate) fn changed_window(
|
/// - [`Window::focused`] cannot be manually changed to `false` after the window is created.
|
||||||
|
pub(crate) fn changed_windows(
|
||||||
mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed<Window>>,
|
mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed<Window>>,
|
||||||
winit_windows: NonSendMut<WinitWindows>,
|
winit_windows: NonSendMut<WinitWindows>,
|
||||||
) {
|
) {
|
||||||
|
@ -1,57 +1,66 @@
|
|||||||
use bevy_ecs::system::Resource;
|
use bevy_ecs::system::Resource;
|
||||||
use bevy_utils::Duration;
|
use bevy_utils::Duration;
|
||||||
|
|
||||||
/// A resource for configuring usage of the [`winit`] library.
|
/// Settings for the [`WinitPlugin`](super::WinitPlugin).
|
||||||
#[derive(Debug, Resource)]
|
#[derive(Debug, Resource)]
|
||||||
pub struct WinitSettings {
|
pub struct WinitSettings {
|
||||||
/// Configures `winit` to return control to the caller after exiting the
|
/// Controls how the [`EventLoop`](winit::event_loop::EventLoop) is deployed.
|
||||||
/// event loop, enabling [`App::run()`](bevy_app::App::run()) to return.
|
|
||||||
///
|
///
|
||||||
/// By default, [`return_from_run`](Self::return_from_run) is `false` and *Bevy*
|
/// - If this value is set to `false` (default), [`run`] is called, and exiting the loop will
|
||||||
/// will use `winit`'s
|
/// terminate the program.
|
||||||
/// [`EventLoop::run()`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run)
|
/// - If this value is set to `true`, [`run_return`] is called, and exiting the loop will
|
||||||
/// to initiate the event loop.
|
/// return control to the caller.
|
||||||
/// [`EventLoop::run()`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run)
|
|
||||||
/// will never return but will terminate the process after the event loop exits.
|
|
||||||
///
|
///
|
||||||
/// Setting [`return_from_run`](Self::return_from_run) to `true` will cause *Bevy*
|
/// **Note:** This cannot be changed while the loop is running. `winit` also discourages use of
|
||||||
/// to use `winit`'s
|
/// `run_return`.
|
||||||
/// [`EventLoopExtRunReturn::run_return()`](https://docs.rs/winit/latest/winit/platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return)
|
|
||||||
/// instead which is strongly discouraged by the `winit` authors.
|
|
||||||
///
|
///
|
||||||
/// # Supported platforms
|
/// # Supported platforms
|
||||||
///
|
///
|
||||||
/// This feature is only available on the following desktop `target_os` configurations:
|
/// `run_return` is only available on the following `target_os` environments:
|
||||||
/// `windows`, `macos`, `linux`, `dragonfly`, `freebsd`, `netbsd`, and `openbsd`.
|
/// - `windows`
|
||||||
|
/// - `macos`
|
||||||
|
/// - `linux`
|
||||||
|
/// - `freebsd`
|
||||||
|
/// - `openbsd`
|
||||||
|
/// - `netbsd`
|
||||||
|
/// - `dragonfly`
|
||||||
///
|
///
|
||||||
/// Setting [`return_from_run`](Self::return_from_run) to `true` on
|
/// The runner will panic if this is set to `true` on other platforms.
|
||||||
/// unsupported platforms will cause [`App::run()`](bevy_app::App::run()) to panic!
|
///
|
||||||
|
/// [`run`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run
|
||||||
|
/// [`run_return`]: https://docs.rs/winit/latest/winit/platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return
|
||||||
pub return_from_run: bool,
|
pub return_from_run: bool,
|
||||||
/// Configures how the winit event loop updates while the window is focused.
|
/// Determines how frequently the application can update when it has focus.
|
||||||
pub focused_mode: UpdateMode,
|
pub focused_mode: UpdateMode,
|
||||||
/// Configures how the winit event loop updates while the window is *not* focused.
|
/// Determines how frequently the application can update when it's out of focus.
|
||||||
pub unfocused_mode: UpdateMode,
|
pub unfocused_mode: UpdateMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WinitSettings {
|
impl WinitSettings {
|
||||||
/// Configure winit with common settings for a game.
|
/// Default settings for games.
|
||||||
pub fn game() -> Self {
|
pub fn game() -> Self {
|
||||||
WinitSettings::default()
|
WinitSettings::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure winit with common settings for a desktop application.
|
/// Default settings for desktop applications.
|
||||||
|
///
|
||||||
|
/// [`Reactive`](UpdateMode::Reactive) if windows have focus,
|
||||||
|
/// [`ReactiveLowPower`](UpdateMode::ReactiveLowPower) otherwise.
|
||||||
pub fn desktop_app() -> Self {
|
pub fn desktop_app() -> Self {
|
||||||
WinitSettings {
|
WinitSettings {
|
||||||
focused_mode: UpdateMode::Reactive {
|
focused_mode: UpdateMode::Reactive {
|
||||||
max_wait: Duration::from_secs(5),
|
wait: Duration::from_secs(5),
|
||||||
},
|
},
|
||||||
unfocused_mode: UpdateMode::ReactiveLowPower {
|
unfocused_mode: UpdateMode::ReactiveLowPower {
|
||||||
max_wait: Duration::from_secs(60),
|
wait: Duration::from_secs(60),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the configured [`UpdateMode`] depending on whether the window is focused or not
|
/// Returns the current [`UpdateMode`].
|
||||||
|
///
|
||||||
|
/// **Note:** The output depends on whether the window has focus or not.
|
||||||
pub fn update_mode(&self, focused: bool) -> &UpdateMode {
|
pub fn update_mode(&self, focused: bool) -> &UpdateMode {
|
||||||
match focused {
|
match focused {
|
||||||
true => &self.focused_mode,
|
true => &self.focused_mode,
|
||||||
@ -59,6 +68,7 @@ impl WinitSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WinitSettings {
|
impl Default for WinitSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
WinitSettings {
|
WinitSettings {
|
||||||
@ -69,45 +79,45 @@ impl Default for WinitSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure how the winit event loop should update.
|
#[allow(clippy::doc_markdown)]
|
||||||
#[derive(Debug)]
|
/// Determines how frequently an [`App`](bevy_app::App) should update.
|
||||||
|
///
|
||||||
|
/// **Note:** This setting is independent of VSync. VSync is controlled by a window's
|
||||||
|
/// [`PresentMode`](bevy_window::PresentMode) setting. If an app can update faster than the refresh
|
||||||
|
/// rate, but VSync is enabled, the update rate will be indirectly limited by the renderer.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum UpdateMode {
|
pub enum UpdateMode {
|
||||||
/// The event loop will update continuously, running as fast as possible.
|
/// The [`App`](bevy_app::App) will update over and over, as fast as it possibly can, until an
|
||||||
|
/// [`AppExit`](bevy_app::AppExit) event appears.
|
||||||
Continuous,
|
Continuous,
|
||||||
/// The event loop will only update if there is a winit event, a redraw is requested, or the
|
/// The [`App`](bevy_app::App) will update in response to the following, until an
|
||||||
/// maximum wait time has elapsed.
|
/// [`AppExit`](bevy_app::AppExit) event appears:
|
||||||
///
|
/// - `wait` time has elapsed since the previous update
|
||||||
/// ## Note
|
/// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw)
|
||||||
///
|
/// - new [window](`winit::event::WindowEvent`) or [raw input](`winit::event::DeviceEvent`)
|
||||||
/// Once the app has executed all bevy systems and reaches the end of the event loop, there is
|
/// events have appeared
|
||||||
/// no way to force the app to wake and update again, unless a `winit` event (such as user
|
|
||||||
/// input, or the window being resized) is received or the time limit is reached.
|
|
||||||
Reactive {
|
Reactive {
|
||||||
/// The maximum time to wait before the event loop runs again.
|
/// The minimum time from the start of one update to the next.
|
||||||
///
|
///
|
||||||
/// Note that Bevy will wait indefinitely if the duration is too high (such as [`Duration::MAX`]).
|
/// **Note:** This has no upper limit.
|
||||||
max_wait: Duration,
|
/// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].
|
||||||
|
wait: Duration,
|
||||||
},
|
},
|
||||||
/// The event loop will only update if there is a winit event from direct interaction with the
|
/// The [`App`](bevy_app::App) will update in response to the following, until an
|
||||||
/// window (e.g. mouseover), a redraw is requested, or the maximum wait time has elapsed.
|
/// [`AppExit`](bevy_app::AppExit) event appears:
|
||||||
|
/// - `wait` time has elapsed since the previous update
|
||||||
|
/// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw)
|
||||||
|
/// - new [window events](`winit::event::WindowEvent`) have appeared
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// **Note:** Unlike [`Reactive`](`UpdateMode::Reactive`), this mode will ignore events that
|
||||||
///
|
/// don't come from interacting with a window, like [`MouseMotion`](winit::event::DeviceEvent::MouseMotion).
|
||||||
/// Once the app has executed all bevy systems and reaches the end of the event loop, there is
|
/// Use this mode if, for example, you only want your app to update when the mouse cursor is
|
||||||
/// no way to force the app to wake and update again, unless a `winit` event (such as user
|
/// moving over a window, not just moving in general. This can greatly reduce power consumption.
|
||||||
/// input, or the window being resized) is received or the time limit is reached.
|
|
||||||
///
|
|
||||||
/// ## Differences from [`UpdateMode::Reactive`]
|
|
||||||
///
|
|
||||||
/// Unlike [`UpdateMode::Reactive`], this mode will ignore winit events that aren't directly
|
|
||||||
/// caused by interaction with the window. For example, you might want to use this mode when the
|
|
||||||
/// window is not focused, to only re-draw your bevy app when the cursor is over the window, but
|
|
||||||
/// not when the mouse moves somewhere else on the screen. This helps to significantly reduce
|
|
||||||
/// power consumption by only updated the app when absolutely necessary.
|
|
||||||
ReactiveLowPower {
|
ReactiveLowPower {
|
||||||
/// The maximum time to wait before the event loop runs again.
|
/// The minimum time from the start of one update to the next.
|
||||||
///
|
///
|
||||||
/// Note that Bevy will wait indefinitely if the duration is too high (such as [`Duration::MAX`]).
|
/// **Note:** This has no upper limit.
|
||||||
max_wait: Duration,
|
/// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].
|
||||||
|
wait: Duration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@ use crate::{
|
|||||||
converters::{convert_enabled_buttons, convert_window_level, convert_window_theme},
|
converters::{convert_enabled_buttons, convert_window_level, convert_window_theme},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A resource which maps window entities to [`winit`] library windows.
|
/// A resource mapping window entities to their [`winit`]-backend [`Window`](winit::window::Window)
|
||||||
|
/// states.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct WinitWindows {
|
pub struct WinitWindows {
|
||||||
/// Stores [`winit`] windows by window identifier.
|
/// Stores [`winit`] windows by window identifier.
|
||||||
@ -30,10 +31,9 @@ pub struct WinitWindows {
|
|||||||
pub entity_to_winit: HashMap<Entity, winit::window::WindowId>,
|
pub entity_to_winit: HashMap<Entity, winit::window::WindowId>,
|
||||||
/// Maps `winit` window identifiers to entities.
|
/// Maps `winit` window identifiers to entities.
|
||||||
pub winit_to_entity: HashMap<winit::window::WindowId, Entity>,
|
pub winit_to_entity: HashMap<winit::window::WindowId, Entity>,
|
||||||
|
// Many `winit` window functions (e.g. `set_window_icon`) can only be called on the main thread.
|
||||||
// Some winit functions, such as `set_window_icon` can only be used from the main thread. If
|
// If they're called on other threads, the program might hang. This marker indicates that this
|
||||||
// they are used in another thread, the app will hang. This marker ensures `WinitWindows` is
|
// type is not thread-safe and will be `!Send` and `!Sync`.
|
||||||
// only ever accessed with bevy's non-send functions and in NonSend systems.
|
|
||||||
_not_send_sync: core::marker::PhantomData<*const ()>,
|
_not_send_sync: core::marker::PhantomData<*const ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,15 +169,15 @@ impl WinitWindows {
|
|||||||
handlers.insert(entity, handler);
|
handlers.insert(entity, handler);
|
||||||
winit_window.set_visible(true);
|
winit_window.set_visible(true);
|
||||||
|
|
||||||
// Do not set the grab mode on window creation if it's none, this can fail on mobile
|
// Do not set the grab mode on window creation if it's none. It can fail on mobile.
|
||||||
if window.cursor.grab_mode != CursorGrabMode::None {
|
if window.cursor.grab_mode != CursorGrabMode::None {
|
||||||
attempt_grab(&winit_window, window.cursor.grab_mode);
|
attempt_grab(&winit_window, window.cursor.grab_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
winit_window.set_cursor_visible(window.cursor.visible);
|
winit_window.set_cursor_visible(window.cursor.visible);
|
||||||
|
|
||||||
// Do not set the cursor hittest on window creation if it's false, as it will always fail on some
|
// Do not set the cursor hittest on window creation if it's false, as it will always fail on
|
||||||
// platforms and log an unfixable warning.
|
// some platforms and log an unfixable warning.
|
||||||
if !window.cursor.hit_test {
|
if !window.cursor.hit_test {
|
||||||
if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) {
|
if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) {
|
||||||
warn!(
|
warn!(
|
||||||
@ -231,7 +231,7 @@ impl WinitWindows {
|
|||||||
/// This should mostly just be called when the window is closing.
|
/// This should mostly just be called when the window is closing.
|
||||||
pub fn remove_window(&mut self, entity: Entity) -> Option<winit::window::Window> {
|
pub fn remove_window(&mut self, entity: Entity) -> Option<winit::window::Window> {
|
||||||
let winit_id = self.entity_to_winit.remove(&entity)?;
|
let winit_id = self.entity_to_winit.remove(&entity)?;
|
||||||
// Don't remove from winit_to_window_id, to track that we used to know about this winit window
|
// Don't remove from `winit_to_window_id` so we know the window used to exist.
|
||||||
self.windows.remove(&winit_id)
|
self.windows.remove(&winit_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -346,8 +346,8 @@ pub fn winit_window_position(
|
|||||||
if let Some(monitor) = maybe_monitor {
|
if let Some(monitor) = maybe_monitor {
|
||||||
let screen_size = monitor.size();
|
let screen_size = monitor.size();
|
||||||
|
|
||||||
// We use the monitors scale factor here since WindowResolution.scale_factor
|
// We use the monitors scale factor here since `WindowResolution.scale_factor` is
|
||||||
// is not yet populated when windows are created at plugin setup
|
// not yet populated when windows are created during plugin setup.
|
||||||
let scale_factor = monitor.scale_factor();
|
let scale_factor = monitor.scale_factor();
|
||||||
|
|
||||||
// Logical to physical window size
|
// Logical to physical window size
|
||||||
|
@ -20,7 +20,7 @@ fn main() {
|
|||||||
.insert_resource(WinitSettings {
|
.insert_resource(WinitSettings {
|
||||||
focused_mode: bevy::winit::UpdateMode::Continuous,
|
focused_mode: bevy::winit::UpdateMode::Continuous,
|
||||||
unfocused_mode: bevy::winit::UpdateMode::ReactiveLowPower {
|
unfocused_mode: bevy::winit::UpdateMode::ReactiveLowPower {
|
||||||
max_wait: Duration::from_millis(10),
|
wait: Duration::from_millis(10),
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user