mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
fix: upgrade to winit v0.30 (#13366)
# Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
This commit is contained in:
parent
44c8cc66c4
commit
061bee7e3c
25 changed files with 1357 additions and 1029 deletions
15
Cargo.toml
15
Cargo.toml
|
@ -359,6 +359,10 @@ argh = "0.1.12"
|
|||
thiserror = "1.0"
|
||||
event-listener = "5.3.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
web-sys = { version = "0.3", features = ["Window"] }
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "examples/hello_world.rs"
|
||||
|
@ -2836,6 +2840,17 @@ description = "Creates a solid color window"
|
|||
category = "Window"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "custom_user_event"
|
||||
path = "examples/window/custom_user_event.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.custom_user_event]
|
||||
name = "Custom User Event"
|
||||
description = "Handles custom user events within the event loop"
|
||||
category = "Window"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "low_power"
|
||||
path = "examples/window/low_power.rs"
|
||||
|
|
|
@ -14,7 +14,7 @@ bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
|
|||
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
|
||||
|
||||
accesskit = "0.12"
|
||||
accesskit = "0.14"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -69,7 +69,7 @@ impl PluginGroup for DefaultPlugins {
|
|||
|
||||
#[cfg(feature = "bevy_winit")]
|
||||
{
|
||||
group = group.add(bevy_winit::WinitPlugin::default());
|
||||
group = group.add::<bevy_winit::WinitPlugin>(bevy_winit::WinitPlugin::default());
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_render")]
|
||||
|
|
|
@ -58,7 +58,7 @@ use bevy_utils::prelude::default;
|
|||
pub use extract_param::Extract;
|
||||
|
||||
use bevy_hierarchy::ValidParentCheckPlugin;
|
||||
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
||||
use bevy_window::{PrimaryWindow, RawHandleWrapperHolder};
|
||||
use extract_resource::ExtractResourcePlugin;
|
||||
use globals::GlobalsPlugin;
|
||||
use render_asset::RenderAssetBytesPerFrame;
|
||||
|
@ -268,10 +268,9 @@ impl Plugin for RenderPlugin {
|
|||
));
|
||||
|
||||
let mut system_state: SystemState<
|
||||
Query<&RawHandleWrapper, With<PrimaryWindow>>,
|
||||
Query<&RawHandleWrapperHolder, With<PrimaryWindow>>,
|
||||
> = SystemState::new(app.world_mut());
|
||||
let primary_window = system_state.get(app.world()).get_single().ok().cloned();
|
||||
|
||||
let settings = render_creation.clone();
|
||||
let async_renderer = async move {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
|
@ -282,11 +281,20 @@ impl Plugin for RenderPlugin {
|
|||
});
|
||||
|
||||
// SAFETY: Plugins should be set up on the main thread.
|
||||
let surface = primary_window.map(|wrapper| unsafe {
|
||||
let handle = wrapper.get_handle();
|
||||
instance
|
||||
.create_surface(handle)
|
||||
.expect("Failed to create wgpu surface")
|
||||
let surface = primary_window.and_then(|wrapper| unsafe {
|
||||
let maybe_handle = wrapper.0.lock().expect(
|
||||
"Couldn't get the window handle in time for renderer initialization",
|
||||
);
|
||||
if let Some(wrapper) = maybe_handle.as_ref() {
|
||||
let handle = wrapper.get_handle();
|
||||
Some(
|
||||
instance
|
||||
.create_surface(handle)
|
||||
.expect("Failed to create wgpu surface"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let request_adapter_options = wgpu::RequestAdapterOptions {
|
||||
|
|
|
@ -22,7 +22,7 @@ concurrent-queue = { version = "2.0.0", optional = true }
|
|||
wasm-bindgen-futures = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
web-time = { version = "0.2" }
|
||||
web-time = { version = "1.1" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -14,7 +14,7 @@ detailed_trace = []
|
|||
[dependencies]
|
||||
ahash = "0.8.7"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
web-time = { version = "0.2" }
|
||||
web-time = { version = "1.1" }
|
||||
hashbrown = { version = "0.14", features = ["serde"] }
|
||||
bevy_utils_proc_macros = { version = "0.14.0-dev", path = "macros" }
|
||||
thread_local = "1.0"
|
||||
|
|
|
@ -388,13 +388,28 @@ pub struct WindowThemeChanged {
|
|||
derive(serde::Serialize, serde::Deserialize),
|
||||
reflect(Serialize, Deserialize)
|
||||
)]
|
||||
pub enum ApplicationLifetime {
|
||||
/// The application just started.
|
||||
Started,
|
||||
pub enum AppLifecycle {
|
||||
/// The application is not started yet.
|
||||
Idle,
|
||||
/// The application is running.
|
||||
Running,
|
||||
/// The application is going to be suspended.
|
||||
/// Applications have one frame to react to this event before being paused in the background.
|
||||
WillSuspend,
|
||||
/// The application was suspended.
|
||||
///
|
||||
/// On Android, applications have one frame to react to this event before being paused in the background.
|
||||
Suspended,
|
||||
/// The application was resumed.
|
||||
Resumed,
|
||||
/// The application is going to be resumed.
|
||||
/// Applications have one extra frame to react to this event before being fully resumed.
|
||||
WillResume,
|
||||
}
|
||||
|
||||
impl AppLifecycle {
|
||||
/// Return `true` if the app can be updated.
|
||||
#[inline]
|
||||
pub fn is_active(&self) -> bool {
|
||||
match self {
|
||||
Self::Idle | Self::Suspended => false,
|
||||
Self::Running | Self::WillSuspend | Self::WillResume => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
//! The [`WindowPlugin`] sets up some global window-related parameters and
|
||||
//! is part of the [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html).
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use bevy_a11y::Focus;
|
||||
|
||||
mod cursor;
|
||||
|
@ -106,13 +108,16 @@ impl Plugin for WindowPlugin {
|
|||
.add_event::<FileDragAndDrop>()
|
||||
.add_event::<WindowMoved>()
|
||||
.add_event::<WindowThemeChanged>()
|
||||
.add_event::<ApplicationLifetime>();
|
||||
.add_event::<AppLifecycle>();
|
||||
|
||||
if let Some(primary_window) = &self.primary_window {
|
||||
let initial_focus = app
|
||||
.world_mut()
|
||||
.spawn(primary_window.clone())
|
||||
.insert(PrimaryWindow)
|
||||
.insert((
|
||||
PrimaryWindow,
|
||||
RawHandleWrapperHolder(Arc::new(Mutex::new(None))),
|
||||
))
|
||||
.id();
|
||||
if let Some(mut focus) = app.world_mut().get_resource_mut::<Focus>() {
|
||||
**focus = Some(initial_focus);
|
||||
|
@ -153,7 +158,7 @@ impl Plugin for WindowPlugin {
|
|||
.register_type::<FileDragAndDrop>()
|
||||
.register_type::<WindowMoved>()
|
||||
.register_type::<WindowThemeChanged>()
|
||||
.register_type::<ApplicationLifetime>();
|
||||
.register_type::<AppLifecycle>();
|
||||
|
||||
// Register window descriptor and related types
|
||||
app.register_type::<Window>()
|
||||
|
|
|
@ -5,7 +5,12 @@ use raw_window_handle::{
|
|||
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
|
||||
RawWindowHandle, WindowHandle,
|
||||
};
|
||||
use std::{any::Any, marker::PhantomData, ops::Deref, sync::Arc};
|
||||
use std::{
|
||||
any::Any,
|
||||
marker::PhantomData,
|
||||
ops::Deref,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// A wrapper over a window.
|
||||
///
|
||||
|
@ -116,3 +121,7 @@ impl HasDisplayHandle for ThreadLockedRawWindowHandleWrapper {
|
|||
Ok(unsafe { DisplayHandle::borrow_raw(self.0.display_handle) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Holder of the [`RawHandleWrapper`] with wrappers, to allow use in asynchronous context
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub struct RawHandleWrapperHolder(pub Arc<Mutex<Option<RawHandleWrapper>>>);
|
||||
|
|
|
@ -687,10 +687,10 @@ impl Default for WindowResolution {
|
|||
|
||||
impl WindowResolution {
|
||||
/// Creates a new [`WindowResolution`].
|
||||
pub fn new(logical_width: f32, logical_height: f32) -> Self {
|
||||
pub fn new(physical_width: f32, physical_height: f32) -> Self {
|
||||
Self {
|
||||
physical_width: logical_width as u32,
|
||||
physical_height: logical_height as u32,
|
||||
physical_width: physical_width as u32,
|
||||
physical_height: physical_height as u32,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -783,9 +783,7 @@ impl WindowResolution {
|
|||
/// Set the window's scale factor, this may get overridden by the backend.
|
||||
#[inline]
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f32) {
|
||||
let (width, height) = (self.width(), self.height());
|
||||
self.scale_factor = scale_factor;
|
||||
self.set(width, height);
|
||||
}
|
||||
|
||||
/// Set the window's scale factor, this will be used over what the backend decides.
|
||||
|
@ -794,9 +792,7 @@ impl WindowResolution {
|
|||
/// size is not within the limits.
|
||||
#[inline]
|
||||
pub fn set_scale_factor_override(&mut self, scale_factor_override: Option<f32>) {
|
||||
let (width, height) = (self.width(), self.height());
|
||||
self.scale_factor_override = scale_factor_override;
|
||||
self.set(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,8 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
|
|||
|
||||
# other
|
||||
# feature rwh_06 refers to window_raw_handle@v0.6
|
||||
winit = { version = "0.29", default-features = false, features = ["rwh_06"] }
|
||||
accesskit_winit = { version = "0.17", default-features = false, features = [
|
||||
winit = { version = "0.30", default-features = false, features = ["rwh_06"] }
|
||||
accesskit_winit = { version = "0.20", default-features = false, features = [
|
||||
"rwh_06",
|
||||
] }
|
||||
approx = { version = "0.5", default-features = false }
|
||||
|
@ -42,7 +42,7 @@ raw-window-handle = "0.6"
|
|||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
winit = { version = "0.29", default-features = false, features = [
|
||||
winit = { version = "0.30", default-features = false, features = [
|
||||
"android-native-activity",
|
||||
"rwh_06",
|
||||
] }
|
||||
|
|
|
@ -6,10 +6,9 @@ use std::{
|
|||
};
|
||||
|
||||
use accesskit_winit::Adapter;
|
||||
use bevy_a11y::accesskit::{ActivationHandler, DeactivationHandler, Node};
|
||||
use bevy_a11y::{
|
||||
accesskit::{
|
||||
ActionHandler, ActionRequest, NodeBuilder, NodeClassSet, NodeId, Role, Tree, TreeUpdate,
|
||||
},
|
||||
accesskit::{ActionHandler, ActionRequest, NodeBuilder, NodeId, Role, Tree, TreeUpdate},
|
||||
AccessibilityNode, AccessibilityRequested, AccessibilitySystem, Focus,
|
||||
};
|
||||
use bevy_a11y::{ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates};
|
||||
|
@ -20,7 +19,7 @@ use bevy_ecs::{
|
|||
prelude::{DetectChanges, Entity, EventReader, EventWriter},
|
||||
query::With,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{NonSend, NonSendMut, Query, Res, ResMut, Resource},
|
||||
system::{NonSendMut, Query, Res, ResMut, Resource},
|
||||
};
|
||||
use bevy_hierarchy::{Children, Parent};
|
||||
use bevy_window::{PrimaryWindow, Window, WindowClosed};
|
||||
|
@ -29,13 +28,78 @@ use bevy_window::{PrimaryWindow, Window, WindowClosed};
|
|||
#[derive(Default, Deref, DerefMut)]
|
||||
pub struct AccessKitAdapters(pub EntityHashMap<Adapter>);
|
||||
|
||||
/// Maps window entities to their respective [`WinitActionHandler`]s.
|
||||
/// Maps window entities to their respective [`WinitActionRequests`]s.
|
||||
#[derive(Resource, Default, Deref, DerefMut)]
|
||||
pub struct WinitActionHandlers(pub EntityHashMap<WinitActionHandler>);
|
||||
pub struct WinitActionRequestHandlers(pub EntityHashMap<Arc<Mutex<WinitActionRequestHandler>>>);
|
||||
|
||||
/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel.
|
||||
#[derive(Clone, Default, Deref, DerefMut)]
|
||||
pub struct WinitActionHandler(pub Arc<Mutex<VecDeque<ActionRequest>>>);
|
||||
pub struct WinitActionRequestHandler(pub VecDeque<ActionRequest>);
|
||||
|
||||
impl WinitActionRequestHandler {
|
||||
fn new() -> Arc<Mutex<Self>> {
|
||||
Arc::new(Mutex::new(Self(VecDeque::new())))
|
||||
}
|
||||
}
|
||||
|
||||
struct AccessKitState {
|
||||
name: String,
|
||||
entity: Entity,
|
||||
requested: AccessibilityRequested,
|
||||
}
|
||||
|
||||
impl AccessKitState {
|
||||
fn new(
|
||||
name: impl Into<String>,
|
||||
entity: Entity,
|
||||
requested: AccessibilityRequested,
|
||||
) -> Arc<Mutex<Self>> {
|
||||
let name = name.into();
|
||||
|
||||
Arc::new(Mutex::new(Self {
|
||||
name,
|
||||
entity,
|
||||
requested,
|
||||
}))
|
||||
}
|
||||
|
||||
fn build_root(&mut self) -> Node {
|
||||
let mut builder = NodeBuilder::new(Role::Window);
|
||||
builder.set_name(self.name.clone());
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn build_initial_tree(&mut self) -> TreeUpdate {
|
||||
let root = self.build_root();
|
||||
let accesskit_window_id = NodeId(self.entity.to_bits());
|
||||
let mut tree = Tree::new(accesskit_window_id);
|
||||
tree.app_name = Some(self.name.clone());
|
||||
self.requested.set(true);
|
||||
|
||||
TreeUpdate {
|
||||
nodes: vec![(accesskit_window_id, root)],
|
||||
tree: Some(tree),
|
||||
focus: accesskit_window_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WinitActivationHandler(Arc<Mutex<AccessKitState>>);
|
||||
|
||||
impl ActivationHandler for WinitActivationHandler {
|
||||
fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
|
||||
Some(self.0.lock().unwrap().build_initial_tree())
|
||||
}
|
||||
}
|
||||
|
||||
impl WinitActivationHandler {
|
||||
pub fn new(state: Arc<Mutex<AccessKitState>>) -> Self {
|
||||
Self(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct WinitActionHandler(Arc<Mutex<WinitActionRequestHandler>>);
|
||||
|
||||
impl ActionHandler for WinitActionHandler {
|
||||
fn do_action(&mut self, request: ActionRequest) {
|
||||
|
@ -44,6 +108,18 @@ impl ActionHandler for WinitActionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl WinitActionHandler {
|
||||
pub fn new(handler: Arc<Mutex<WinitActionRequestHandler>>) -> Self {
|
||||
Self(handler)
|
||||
}
|
||||
}
|
||||
|
||||
struct WinitDeactivationHandler;
|
||||
|
||||
impl DeactivationHandler for WinitDeactivationHandler {
|
||||
fn deactivate_accessibility(&mut self) {}
|
||||
}
|
||||
|
||||
/// Prepares accessibility for a winit window.
|
||||
pub(crate) fn prepare_accessibility_for_window(
|
||||
winit_window: &winit::window::Window,
|
||||
|
@ -51,43 +127,39 @@ pub(crate) fn prepare_accessibility_for_window(
|
|||
name: String,
|
||||
accessibility_requested: AccessibilityRequested,
|
||||
adapters: &mut AccessKitAdapters,
|
||||
handlers: &mut WinitActionHandlers,
|
||||
handlers: &mut WinitActionRequestHandlers,
|
||||
) {
|
||||
let mut root_builder = NodeBuilder::new(Role::Window);
|
||||
root_builder.set_name(name.into_boxed_str());
|
||||
let root = root_builder.build(&mut NodeClassSet::lock_global());
|
||||
let state = AccessKitState::new(name, entity, accessibility_requested);
|
||||
let activation_handler = WinitActivationHandler::new(Arc::clone(&state));
|
||||
|
||||
let accesskit_window_id = NodeId(entity.to_bits());
|
||||
let handler = WinitActionHandler::default();
|
||||
let adapter = Adapter::with_action_handler(
|
||||
let action_request_handler = WinitActionRequestHandler::new();
|
||||
let action_handler = WinitActionHandler::new(Arc::clone(&action_request_handler));
|
||||
let deactivation_handler = WinitDeactivationHandler;
|
||||
|
||||
let adapter = Adapter::with_direct_handlers(
|
||||
winit_window,
|
||||
move || {
|
||||
accessibility_requested.set(true);
|
||||
TreeUpdate {
|
||||
nodes: vec![(accesskit_window_id, root)],
|
||||
tree: Some(Tree::new(accesskit_window_id)),
|
||||
focus: accesskit_window_id,
|
||||
}
|
||||
},
|
||||
Box::new(handler.clone()),
|
||||
activation_handler,
|
||||
action_handler,
|
||||
deactivation_handler,
|
||||
);
|
||||
|
||||
adapters.insert(entity, adapter);
|
||||
handlers.insert(entity, handler);
|
||||
handlers.insert(entity, action_request_handler);
|
||||
}
|
||||
|
||||
fn window_closed(
|
||||
mut adapters: NonSendMut<AccessKitAdapters>,
|
||||
mut receivers: ResMut<WinitActionHandlers>,
|
||||
mut handlers: ResMut<WinitActionRequestHandlers>,
|
||||
mut events: EventReader<WindowClosed>,
|
||||
) {
|
||||
for WindowClosed { window, .. } in events.read() {
|
||||
adapters.remove(window);
|
||||
receivers.remove(window);
|
||||
handlers.remove(window);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_receivers(
|
||||
handlers: Res<WinitActionHandlers>,
|
||||
handlers: Res<WinitActionRequestHandlers>,
|
||||
mut actions: EventWriter<ActionRequestWrapper>,
|
||||
) {
|
||||
for (_id, handler) in handlers.iter() {
|
||||
|
@ -106,7 +178,7 @@ fn should_update_accessibility_nodes(
|
|||
}
|
||||
|
||||
fn update_accessibility_nodes(
|
||||
adapters: NonSend<AccessKitAdapters>,
|
||||
mut adapters: NonSendMut<AccessKitAdapters>,
|
||||
focus: Res<Focus>,
|
||||
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
|
||||
nodes: Query<(
|
||||
|
@ -120,7 +192,7 @@ fn update_accessibility_nodes(
|
|||
let Ok((primary_window_id, primary_window)) = primary_window.get_single() else {
|
||||
return;
|
||||
};
|
||||
let Some(adapter) = adapters.get(&primary_window_id) else {
|
||||
let Some(adapter) = adapters.get_mut(&primary_window_id) else {
|
||||
return;
|
||||
};
|
||||
if focus.is_changed() || !nodes.is_empty() {
|
||||
|
@ -155,7 +227,7 @@ fn update_adapter(
|
|||
queue_node_for_update(entity, parent, &node_entities, &mut window_children);
|
||||
add_children_nodes(children, &node_entities, &mut node);
|
||||
let node_id = NodeId(entity.to_bits());
|
||||
let node = node.build(&mut NodeClassSet::lock_global());
|
||||
let node = node.build();
|
||||
to_update.push((node_id, node));
|
||||
}
|
||||
let mut window_node = NodeBuilder::new(Role::Window);
|
||||
|
@ -164,7 +236,7 @@ fn update_adapter(
|
|||
window_node.set_name(title.into_boxed_str());
|
||||
}
|
||||
window_node.set_children(window_children);
|
||||
let window_node = window_node.build(&mut NodeClassSet::lock_global());
|
||||
let window_node = window_node.build();
|
||||
let node_id = NodeId(primary_window_id.to_bits());
|
||||
let window_update = (node_id, window_node);
|
||||
to_update.insert(0, window_update);
|
||||
|
@ -214,7 +286,7 @@ pub struct AccessKitPlugin;
|
|||
impl Plugin for AccessKitPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_non_send_resource::<AccessKitAdapters>()
|
||||
.init_resource::<WinitActionHandlers>()
|
||||
.init_resource::<WinitActionRequestHandlers>()
|
||||
.add_event::<ActionRequestWrapper>()
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
|
|
|
@ -12,60 +12,33 @@
|
|||
//! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`].
|
||||
//! See `winit_runner` for details.
|
||||
|
||||
pub mod accessibility;
|
||||
mod converters;
|
||||
mod system;
|
||||
mod winit_config;
|
||||
pub mod winit_event;
|
||||
mod winit_windows;
|
||||
use bevy_window::RawHandleWrapperHolder;
|
||||
use std::marker::PhantomData;
|
||||
use winit::event_loop::EventLoop;
|
||||
#[cfg(target_os = "android")]
|
||||
pub use winit::platform::android::activity as android_activity;
|
||||
|
||||
use std::sync::mpsc::{sync_channel, SyncSender};
|
||||
|
||||
use approx::relative_eq;
|
||||
use bevy_a11y::AccessibilityRequested;
|
||||
use bevy_utils::Instant;
|
||||
use bevy_app::{App, Last, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
#[allow(deprecated)]
|
||||
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
|
||||
pub use system::create_windows;
|
||||
use system::{changed_windows, despawn_windows, CachedWindow};
|
||||
use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
use system::{changed_windows, despawn_windows};
|
||||
pub use winit_config::*;
|
||||
pub use winit_event::*;
|
||||
pub use winit_windows::*;
|
||||
|
||||
use bevy_app::{App, AppExit, Last, Plugin, PluginsState};
|
||||
use bevy_ecs::event::ManualEventReader;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::system::SystemState;
|
||||
use bevy_input::{
|
||||
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
|
||||
touchpad::{TouchpadMagnify, TouchpadRotate},
|
||||
};
|
||||
use bevy_math::{ivec2, DVec2, Vec2};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use bevy_tasks::tick_global_task_pools_on_main_thread;
|
||||
use bevy_utils::tracing::{error, trace, warn};
|
||||
#[allow(deprecated)]
|
||||
use bevy_window::{
|
||||
exit_on_all_closed, ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved,
|
||||
FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window,
|
||||
WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed,
|
||||
WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged,
|
||||
WindowThemeChanged,
|
||||
};
|
||||
#[cfg(target_os = "android")]
|
||||
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
||||
use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers};
|
||||
use crate::state::winit_runner;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub use winit::platform::android::activity as android_activity;
|
||||
|
||||
use winit::event::StartCause;
|
||||
use winit::{
|
||||
event::{self, DeviceEvent, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionHandlers};
|
||||
|
||||
use crate::converters::convert_winit_theme;
|
||||
pub mod accessibility;
|
||||
mod converters;
|
||||
mod state;
|
||||
mod system;
|
||||
mod winit_config;
|
||||
pub mod winit_event;
|
||||
mod winit_windows;
|
||||
|
||||
/// [`AndroidApp`] provides an interface to query the application state as well as monitor events
|
||||
/// (for example lifecycle and input events).
|
||||
|
@ -79,8 +52,11 @@ pub static ANDROID_APP: std::sync::OnceLock<android_activity::AndroidApp> =
|
|||
/// This plugin will add systems and resources that sync with the `winit` backend and also
|
||||
/// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to
|
||||
/// receive window and input events from the OS.
|
||||
///
|
||||
/// The `T` event type can be used to pass custom events to the `winit`'s loop, and handled as events
|
||||
/// in systems.
|
||||
#[derive(Default)]
|
||||
pub struct WinitPlugin {
|
||||
pub struct WinitPlugin<T: Event = WakeUp> {
|
||||
/// Allows the window (and the event loop) to be created on any thread
|
||||
/// instead of only the main thread.
|
||||
///
|
||||
|
@ -91,18 +67,23 @@ pub struct WinitPlugin {
|
|||
/// Only works on Linux (X11/Wayland) and Windows.
|
||||
/// This field is ignored on other platforms.
|
||||
pub run_on_any_thread: bool,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl Plugin for WinitPlugin {
|
||||
impl<T: Event> Plugin for WinitPlugin<T> {
|
||||
fn name(&self) -> &str {
|
||||
"bevy_winit::WinitPlugin"
|
||||
}
|
||||
|
||||
fn build(&self, app: &mut App) {
|
||||
let mut event_loop_builder = EventLoopBuilder::<UserEvent>::with_user_event();
|
||||
let mut event_loop_builder = EventLoop::<T>::with_user_event();
|
||||
|
||||
// linux check is needed because x11 might be enabled on other platforms.
|
||||
#[cfg(all(target_os = "linux", feature = "x11"))]
|
||||
{
|
||||
use winit::platform::x11::EventLoopBuilderExtX11;
|
||||
|
||||
// This allows a Bevy app to be started and ran outside of the main thread.
|
||||
// This allows a Bevy app to be started and ran outside the main thread.
|
||||
// A use case for this is to allow external applications to spawn a thread
|
||||
// which runs a Bevy app without requiring the Bevy app to need to reside on
|
||||
// the main thread, which can be problematic.
|
||||
|
@ -132,7 +113,7 @@ impl Plugin for WinitPlugin {
|
|||
app.init_non_send_resource::<WinitWindows>()
|
||||
.init_resource::<WinitSettings>()
|
||||
.add_event::<WinitEvent>()
|
||||
.set_runner(winit_runner)
|
||||
.set_runner(winit_runner::<T>)
|
||||
.add_systems(
|
||||
Last,
|
||||
(
|
||||
|
@ -150,691 +131,50 @@ impl Plugin for WinitPlugin {
|
|||
.build()
|
||||
.expect("Failed to build event loop");
|
||||
|
||||
// iOS, macOS, and Android don't like it if you create windows before the event loop is
|
||||
// initialized.
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/rust-windowing/winit/blob/master/README.md#macos
|
||||
// - https://github.com/rust-windowing/winit/blob/master/README.md#ios
|
||||
#[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.
|
||||
let mut create_window = SystemState::<CreateWindowParams>::from_world(app.world_mut());
|
||||
create_windows(&event_loop, create_window.get_mut(app.world_mut()));
|
||||
create_window.apply(app.world_mut());
|
||||
}
|
||||
|
||||
// `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);
|
||||
}
|
||||
}
|
||||
|
||||
trait AppSendEvent {
|
||||
fn send(&mut self, event: impl Into<WinitEvent>);
|
||||
}
|
||||
impl AppSendEvent for Vec<WinitEvent> {
|
||||
fn send(&mut self, event: impl Into<WinitEvent>) {
|
||||
self.push(Into::<WinitEvent>::into(event));
|
||||
}
|
||||
}
|
||||
|
||||
/// Persistent state that is used to run the [`App`] according to the current
|
||||
/// [`UpdateMode`].
|
||||
struct WinitAppRunnerState {
|
||||
/// Current activity state of the app.
|
||||
activity_state: UpdateState,
|
||||
/// Current update mode of the app.
|
||||
update_mode: UpdateMode,
|
||||
/// Is `true` if a new [`WindowEvent`] has been received since the last update.
|
||||
window_event_received: bool,
|
||||
/// Is `true` if a new [`DeviceEvent`] has been received since the last update.
|
||||
device_event_received: bool,
|
||||
/// Is `true` if the app has requested a redraw since the last update.
|
||||
redraw_requested: bool,
|
||||
/// Is `true` if enough time has elapsed since `last_update` to run another update.
|
||||
wait_elapsed: bool,
|
||||
/// Number of "forced" updates to trigger on application start
|
||||
startup_forced_updates: u32,
|
||||
}
|
||||
|
||||
impl WinitAppRunnerState {
|
||||
fn reset_on_update(&mut self) {
|
||||
self.window_event_received = false;
|
||||
self.device_event_received = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WinitAppRunnerState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
activity_state: UpdateState::NotYetStarted,
|
||||
update_mode: UpdateMode::Continuous,
|
||||
window_event_received: false,
|
||||
device_event_received: false,
|
||||
redraw_requested: false,
|
||||
wait_elapsed: false,
|
||||
// 3 seems to be enough, 5 is a safe margin
|
||||
startup_forced_updates: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
enum UpdateState {
|
||||
NotYetStarted,
|
||||
Active,
|
||||
Suspended,
|
||||
WillSuspend,
|
||||
WillResume,
|
||||
}
|
||||
|
||||
impl UpdateState {
|
||||
#[inline]
|
||||
fn is_active(&self) -> bool {
|
||||
match self {
|
||||
Self::NotYetStarted | Self::Suspended => false,
|
||||
Self::Active | Self::WillSuspend | Self::WillResume => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters of the [`create_windows`] system.
|
||||
pub type CreateWindowParams<'w, 's, F = ()> = (
|
||||
Commands<'w, 's>,
|
||||
Query<'w, 's, (Entity, &'static mut Window), F>,
|
||||
EventWriter<'w, WindowCreated>,
|
||||
NonSendMut<'w, WinitWindows>,
|
||||
NonSendMut<'w, AccessKitAdapters>,
|
||||
ResMut<'w, WinitActionHandlers>,
|
||||
Res<'w, AccessibilityRequested>,
|
||||
);
|
||||
/// The default event that can be used to wake the window loop
|
||||
/// Wakes up the loop if in wait state
|
||||
#[derive(Debug, Default, Clone, Copy, Event)]
|
||||
pub struct WakeUp;
|
||||
|
||||
/// The [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`].
|
||||
///
|
||||
/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
|
||||
///
|
||||
/// Use `NonSend<EventLoopProxy>` to receive this resource.
|
||||
pub type EventLoopProxy = winit::event_loop::EventLoopProxy<UserEvent>;
|
||||
pub type EventLoopProxy<T> = winit::event_loop::EventLoopProxy<T>;
|
||||
|
||||
type UserEvent = RequestRedraw;
|
||||
|
||||
/// The default [`App::runner`] for the [`WinitPlugin`] plugin.
|
||||
///
|
||||
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
|
||||
/// `EventLoop`.
|
||||
pub fn winit_runner(mut app: App) -> AppExit {
|
||||
if app.plugins_state() == PluginsState::Ready {
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
}
|
||||
|
||||
let event_loop = app
|
||||
.world_mut()
|
||||
.remove_non_send_resource::<EventLoop<UserEvent>>()
|
||||
.unwrap();
|
||||
|
||||
app.world_mut()
|
||||
.insert_non_send_resource(event_loop.create_proxy());
|
||||
|
||||
let mut runner_state = WinitAppRunnerState::default();
|
||||
|
||||
// Create a channel with a size of 1, since ideally only one exit code will be sent before exiting the app.
|
||||
let (exit_sender, exit_receiver) = sync_channel(1);
|
||||
|
||||
// prepare structures to access data in the world
|
||||
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
||||
|
||||
let mut focused_windows_state: SystemState<(Res<WinitSettings>, Query<(Entity, &Window)>)> =
|
||||
SystemState::new(app.world_mut());
|
||||
|
||||
let mut event_writer_system_state: SystemState<(
|
||||
EventWriter<WindowResized>,
|
||||
NonSend<WinitWindows>,
|
||||
Query<(&mut Window, &mut CachedWindow)>,
|
||||
NonSend<AccessKitAdapters>,
|
||||
)> = SystemState::new(app.world_mut());
|
||||
|
||||
let mut create_window =
|
||||
SystemState::<CreateWindowParams<Added<Window>>>::from_world(app.world_mut());
|
||||
let mut winit_events = Vec::default();
|
||||
// set up the event loop
|
||||
let event_handler = move |event, event_loop: &EventLoopWindowTarget<UserEvent>| {
|
||||
// The event loop is in the process of exiting, so don't deliver any new events
|
||||
if event_loop.exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
handle_winit_event(
|
||||
&mut app,
|
||||
&mut runner_state,
|
||||
&mut create_window,
|
||||
&mut event_writer_system_state,
|
||||
&mut focused_windows_state,
|
||||
&mut redraw_event_reader,
|
||||
&mut winit_events,
|
||||
&exit_sender,
|
||||
event,
|
||||
event_loop,
|
||||
);
|
||||
};
|
||||
|
||||
trace!("starting winit event loop");
|
||||
// TODO(clean): the winit docs mention using `spawn` instead of `run` on WASM.
|
||||
if let Err(err) = event_loop.run(event_handler) {
|
||||
error!("winit event loop returned an error: {err}");
|
||||
}
|
||||
|
||||
// If everything is working correctly then the event loop only exits after it's sent a exit code.
|
||||
exit_receiver
|
||||
.try_recv()
|
||||
.map_err(|err| error!("Failed to receive a app exit code! This is a bug. Reason: {err}"))
|
||||
.unwrap_or(AppExit::error())
|
||||
trait AppSendEvent {
|
||||
fn send(&mut self, event: impl Into<WinitEvent>);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments /* TODO: probs can reduce # of args */)]
|
||||
fn handle_winit_event(
|
||||
app: &mut App,
|
||||
runner_state: &mut WinitAppRunnerState,
|
||||
create_window: &mut SystemState<CreateWindowParams<Added<Window>>>,
|
||||
event_writer_system_state: &mut SystemState<(
|
||||
EventWriter<WindowResized>,
|
||||
NonSend<WinitWindows>,
|
||||
Query<(&mut Window, &mut CachedWindow)>,
|
||||
NonSend<AccessKitAdapters>,
|
||||
)>,
|
||||
focused_windows_state: &mut SystemState<(Res<WinitSettings>, Query<(Entity, &Window)>)>,
|
||||
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
|
||||
winit_events: &mut Vec<WinitEvent>,
|
||||
exit_notify: &SyncSender<AppExit>,
|
||||
event: Event<UserEvent>,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
) {
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
|
||||
|
||||
if app.plugins_state() != PluginsState::Cleaned {
|
||||
if app.plugins_state() != PluginsState::Ready {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tick_global_task_pools_on_main_thread();
|
||||
} else {
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
}
|
||||
runner_state.redraw_requested = true;
|
||||
}
|
||||
|
||||
// create any new windows
|
||||
// (even if app did not update, some may have been created by plugin setup)
|
||||
create_windows(event_loop, create_window.get_mut(app.world_mut()));
|
||||
create_window.apply(app.world_mut());
|
||||
|
||||
match event {
|
||||
Event::AboutToWait => {
|
||||
if let Some(app_redraw_events) = app.world().get_resource::<Events<RequestRedraw>>() {
|
||||
if redraw_event_reader.read(app_redraw_events).last().is_some() {
|
||||
runner_state.redraw_requested = true;
|
||||
}
|
||||
}
|
||||
|
||||
let (config, windows) = focused_windows_state.get(app.world());
|
||||
let focused = windows.iter().any(|(_, window)| window.focused);
|
||||
|
||||
let mut update_mode = config.update_mode(focused);
|
||||
let mut should_update = should_update(runner_state, update_mode);
|
||||
|
||||
if runner_state.startup_forced_updates > 0 {
|
||||
runner_state.startup_forced_updates -= 1;
|
||||
// Ensure that an update is triggered on the first iterations for app initialization
|
||||
should_update = true;
|
||||
}
|
||||
|
||||
if runner_state.activity_state == UpdateState::WillSuspend {
|
||||
runner_state.activity_state = UpdateState::Suspended;
|
||||
// Trigger one last update to enter the suspended state
|
||||
should_update = true;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// Remove the `RawHandleWrapper` from the primary window.
|
||||
// This will trigger the surface destruction.
|
||||
let mut query = app
|
||||
.world_mut()
|
||||
.query_filtered::<Entity, With<PrimaryWindow>>();
|
||||
let entity = query.single(&app.world());
|
||||
app.world_mut()
|
||||
.entity_mut(entity)
|
||||
.remove::<RawHandleWrapper>();
|
||||
}
|
||||
}
|
||||
|
||||
if runner_state.activity_state == UpdateState::WillResume {
|
||||
runner_state.activity_state = UpdateState::Active;
|
||||
// Trigger the update to enter the active state
|
||||
should_update = true;
|
||||
// Trigger the next redraw ro refresh the screen immediately
|
||||
runner_state.redraw_requested = true;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// Get windows that are cached but without raw handles. Those window were already created, but got their
|
||||
// handle wrapper removed when the app was suspended.
|
||||
let mut query = app
|
||||
.world_mut()
|
||||
.query_filtered::<(Entity, &Window), (With<CachedWindow>, Without<bevy_window::RawHandleWrapper>)>();
|
||||
if let Ok((entity, window)) = query.get_single(&app.world()) {
|
||||
let window = window.clone();
|
||||
|
||||
let (
|
||||
..,
|
||||
mut winit_windows,
|
||||
mut adapters,
|
||||
mut handlers,
|
||||
accessibility_requested,
|
||||
) = create_window.get_mut(app.world_mut());
|
||||
|
||||
let winit_window = winit_windows.create_window(
|
||||
event_loop,
|
||||
entity,
|
||||
&window,
|
||||
&mut adapters,
|
||||
&mut handlers,
|
||||
&accessibility_requested,
|
||||
);
|
||||
|
||||
let wrapper = RawHandleWrapper::new(winit_window).unwrap();
|
||||
|
||||
app.world_mut().entity_mut(entity).insert(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is recorded before running app.update(), to run the next cycle after a correct timeout.
|
||||
// If the cycle takes more than the wait timeout, it will be re-executed immediately.
|
||||
let begin_frame_time = Instant::now();
|
||||
|
||||
if should_update {
|
||||
// Not redrawing, but the timeout elapsed.
|
||||
run_app_update(runner_state, app, winit_events);
|
||||
|
||||
// Running the app may have changed the WinitSettings resource, so we have to re-extract it.
|
||||
let (config, windows) = focused_windows_state.get(app.world());
|
||||
let focused = windows.iter().any(|(_, window)| window.focused);
|
||||
|
||||
update_mode = config.update_mode(focused);
|
||||
}
|
||||
|
||||
match update_mode {
|
||||
UpdateMode::Continuous => {
|
||||
// per winit's docs on [Window::is_visible](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.is_visible),
|
||||
// we cannot use the visibility to drive rendering on these platforms
|
||||
// so we cannot discern whether to beneficially use `Poll` or not?
|
||||
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 winit_windows = app.world().non_send_resource::<WinitWindows>();
|
||||
let visible = 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the next redraw to refresh the screen immediately if waiting
|
||||
if let ControlFlow::Wait = event_loop.control_flow() {
|
||||
runner_state.redraw_requested = true;
|
||||
}
|
||||
}
|
||||
UpdateMode::Reactive { wait } | UpdateMode::ReactiveLowPower { wait } => {
|
||||
// Set the next timeout, starting from the instant before running app.update() to avoid frame delays
|
||||
if let Some(next) = begin_frame_time.checked_add(wait) {
|
||||
if runner_state.wait_elapsed {
|
||||
event_loop.set_control_flow(ControlFlow::WaitUntil(next));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if update_mode != runner_state.update_mode {
|
||||
// Trigger the next redraw since we're changing the update mode
|
||||
runner_state.redraw_requested = true;
|
||||
runner_state.update_mode = update_mode;
|
||||
}
|
||||
|
||||
if runner_state.redraw_requested
|
||||
&& runner_state.activity_state != UpdateState::Suspended
|
||||
{
|
||||
let winit_windows = app.world().non_send_resource::<WinitWindows>();
|
||||
for window in winit_windows.windows.values() {
|
||||
window.request_redraw();
|
||||
}
|
||||
runner_state.redraw_requested = false;
|
||||
}
|
||||
}
|
||||
Event::NewEvents(cause) => {
|
||||
runner_state.wait_elapsed = match cause {
|
||||
StartCause::WaitCancelled {
|
||||
requested_resume: Some(resume),
|
||||
..
|
||||
} => {
|
||||
// If the resume time is not after now, it means that at least the wait timeout
|
||||
// has elapsed.
|
||||
resume <= Instant::now()
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event, window_id, ..
|
||||
} => {
|
||||
let (mut window_resized, winit_windows, mut windows, access_kit_adapters) =
|
||||
event_writer_system_state.get_mut(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, _)) = windows.get_mut(window) else {
|
||||
warn!("Window {window:?} is missing `Window` component, skipping event {event:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
// Allow AccessKit to respond to `WindowEvent`s before they reach
|
||||
// the engine.
|
||||
if let Some(adapter) = access_kit_adapters.get(&window) {
|
||||
if let Some(winit_window) = winit_windows.get_window(window) {
|
||||
adapter.process_event(winit_window, &event);
|
||||
}
|
||||
}
|
||||
|
||||
runner_state.window_event_received = true;
|
||||
|
||||
match event {
|
||||
WindowEvent::Resized(size) => {
|
||||
react_to_resize(&mut win, size, &mut window_resized, 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();
|
||||
#[allow(deprecated)]
|
||||
winit_events.send(ReceivedCharacter { window, char });
|
||||
}
|
||||
}
|
||||
winit_events.send(converters::convert_keyboard_input(event, window));
|
||||
}
|
||||
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();
|
||||
winit_events.send(CursorMoved {
|
||||
window,
|
||||
position,
|
||||
delta,
|
||||
});
|
||||
}
|
||||
WindowEvent::CursorEntered { .. } => {
|
||||
winit_events.send(CursorEntered { window });
|
||||
}
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
win.set_physical_cursor_position(None);
|
||||
winit_events.send(CursorLeft { window });
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
winit_events.send(MouseButtonInput {
|
||||
button: converters::convert_mouse_button(button),
|
||||
state: converters::convert_element_state(state),
|
||||
window,
|
||||
});
|
||||
}
|
||||
WindowEvent::TouchpadMagnify { delta, .. } => {
|
||||
winit_events.send(TouchpadMagnify(delta as f32));
|
||||
}
|
||||
WindowEvent::TouchpadRotate { delta, .. } => {
|
||||
winit_events.send(TouchpadRotate(delta));
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||
event::MouseScrollDelta::LineDelta(x, y) => {
|
||||
winit_events.send(MouseWheel {
|
||||
unit: MouseScrollUnit::Line,
|
||||
x,
|
||||
y,
|
||||
window,
|
||||
});
|
||||
}
|
||||
event::MouseScrollDelta::PixelDelta(p) => {
|
||||
winit_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);
|
||||
winit_events.send(converters::convert_touch_input(touch, location, window));
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
mut inner_size_writer,
|
||||
} => {
|
||||
let prior_factor = win.resolution.scale_factor();
|
||||
win.resolution.set_scale_factor(scale_factor as f32);
|
||||
// Note: this may be different from new_scale_factor if
|
||||
// `scale_factor_override` is set to Some(thing)
|
||||
let new_factor = win.resolution.scale_factor();
|
||||
|
||||
let mut new_inner_size =
|
||||
PhysicalSize::new(win.physical_width(), win.physical_height());
|
||||
let scale_factor_override = win.resolution.scale_factor_override();
|
||||
if let Some(forced_factor) = scale_factor_override {
|
||||
// This window is overriding the OS-suggested DPI, so its physical size
|
||||
// should be set based on the overriding value. Its logical size already
|
||||
// incorporates any resize constraints.
|
||||
let maybe_new_inner_size = LogicalSize::new(win.width(), win.height())
|
||||
.to_physical::<u32>(forced_factor as f64);
|
||||
if let Err(err) = inner_size_writer.request_inner_size(new_inner_size) {
|
||||
warn!("Winit Failed to resize the window: {err}");
|
||||
} else {
|
||||
new_inner_size = maybe_new_inner_size;
|
||||
}
|
||||
}
|
||||
let new_logical_width = new_inner_size.width as f32 / new_factor;
|
||||
let new_logical_height = new_inner_size.height as f32 / new_factor;
|
||||
|
||||
let width_equal = relative_eq!(win.width(), new_logical_width);
|
||||
let height_equal = relative_eq!(win.height(), new_logical_height);
|
||||
win.resolution
|
||||
.set_physical_resolution(new_inner_size.width, new_inner_size.height);
|
||||
|
||||
winit_events.send(WindowBackendScaleFactorChanged {
|
||||
window,
|
||||
scale_factor,
|
||||
});
|
||||
if scale_factor_override.is_none() && !relative_eq!(new_factor, prior_factor) {
|
||||
winit_events.send(WindowScaleFactorChanged {
|
||||
window,
|
||||
scale_factor,
|
||||
});
|
||||
}
|
||||
|
||||
if !width_equal || !height_equal {
|
||||
winit_events.send(WindowResized {
|
||||
window,
|
||||
width: new_logical_width,
|
||||
height: new_logical_height,
|
||||
});
|
||||
}
|
||||
}
|
||||
WindowEvent::Focused(focused) => {
|
||||
win.focused = focused;
|
||||
winit_events.send(WindowFocused { window, focused });
|
||||
}
|
||||
WindowEvent::Occluded(occluded) => {
|
||||
winit_events.send(WindowOccluded { window, occluded });
|
||||
}
|
||||
WindowEvent::DroppedFile(path_buf) => {
|
||||
winit_events.send(FileDragAndDrop::DroppedFile { window, path_buf });
|
||||
}
|
||||
WindowEvent::HoveredFile(path_buf) => {
|
||||
winit_events.send(FileDragAndDrop::HoveredFile { window, path_buf });
|
||||
}
|
||||
WindowEvent::HoveredFileCancelled => {
|
||||
winit_events.send(FileDragAndDrop::HoveredFileCanceled { window });
|
||||
}
|
||||
WindowEvent::Moved(position) => {
|
||||
let position = ivec2(position.x, position.y);
|
||||
win.position.set(position);
|
||||
winit_events.send(WindowMoved { window, position });
|
||||
}
|
||||
WindowEvent::Ime(event) => match event {
|
||||
event::Ime::Preedit(value, cursor) => {
|
||||
winit_events.send(Ime::Preedit {
|
||||
window,
|
||||
value,
|
||||
cursor,
|
||||
});
|
||||
}
|
||||
event::Ime::Commit(value) => {
|
||||
winit_events.send(Ime::Commit { window, value });
|
||||
}
|
||||
event::Ime::Enabled => {
|
||||
winit_events.send(Ime::Enabled { window });
|
||||
}
|
||||
event::Ime::Disabled => {
|
||||
winit_events.send(Ime::Disabled { window });
|
||||
}
|
||||
},
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
winit_events.send(WindowThemeChanged {
|
||||
window,
|
||||
theme: convert_winit_theme(theme),
|
||||
});
|
||||
}
|
||||
WindowEvent::Destroyed => {
|
||||
winit_events.send(WindowDestroyed { window });
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
run_app_update(runner_state, app, winit_events);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut windows = app.world_mut().query::<(&mut Window, &mut CachedWindow)>();
|
||||
if let Ok((window_component, mut cache)) = windows.get_mut(app.world_mut(), window) {
|
||||
if window_component.is_changed() {
|
||||
cache.window = window_component.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::DeviceEvent { 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);
|
||||
winit_events.send(MouseMotion { delta });
|
||||
}
|
||||
}
|
||||
Event::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.activity_state = UpdateState::WillSuspend;
|
||||
}
|
||||
Event::Resumed => {
|
||||
match runner_state.activity_state {
|
||||
UpdateState::NotYetStarted => winit_events.send(ApplicationLifetime::Started),
|
||||
_ => winit_events.send(ApplicationLifetime::Resumed),
|
||||
}
|
||||
runner_state.activity_state = UpdateState::WillResume;
|
||||
}
|
||||
Event::UserEvent(RequestRedraw) => {
|
||||
runner_state.redraw_requested = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(app_exit) = app.should_exit() {
|
||||
if let Err(err) = exit_notify.try_send(app_exit) {
|
||||
error!("Failed to send a app exit notification! This is a bug. Reason: {err}");
|
||||
};
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
fn should_update(runner_state: &WinitAppRunnerState, update_mode: UpdateMode) -> bool {
|
||||
let handle_event = match update_mode {
|
||||
UpdateMode::Continuous | UpdateMode::Reactive { .. } => {
|
||||
runner_state.wait_elapsed
|
||||
|| runner_state.window_event_received
|
||||
|| runner_state.device_event_received
|
||||
}
|
||||
UpdateMode::ReactiveLowPower { .. } => {
|
||||
runner_state.wait_elapsed || runner_state.window_event_received
|
||||
}
|
||||
};
|
||||
|
||||
handle_event && runner_state.activity_state.is_active()
|
||||
}
|
||||
|
||||
fn run_app_update(
|
||||
runner_state: &mut WinitAppRunnerState,
|
||||
app: &mut App,
|
||||
winit_events: &mut Vec<WinitEvent>,
|
||||
) {
|
||||
runner_state.reset_on_update();
|
||||
|
||||
forward_winit_events(winit_events, app);
|
||||
|
||||
if app.plugins_state() == PluginsState::Cleaned {
|
||||
app.update();
|
||||
impl AppSendEvent for Vec<WinitEvent> {
|
||||
fn send(&mut self, event: impl Into<WinitEvent>) {
|
||||
self.push(Into::<WinitEvent>::into(event));
|
||||
}
|
||||
}
|
||||
|
||||
fn react_to_resize(
|
||||
win: &mut Mut<'_, Window>,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
window_resized: &mut EventWriter<WindowResized>,
|
||||
window: Entity,
|
||||
) {
|
||||
win.resolution
|
||||
.set_physical_resolution(size.width, size.height);
|
||||
|
||||
window_resized.send(WindowResized {
|
||||
window,
|
||||
width: win.width(),
|
||||
height: win.height(),
|
||||
});
|
||||
}
|
||||
/// The parameters of the [`create_windows`] system.
|
||||
pub type CreateWindowParams<'w, 's, F = ()> = (
|
||||
Commands<'w, 's>,
|
||||
Query<
|
||||
'w,
|
||||
's,
|
||||
(
|
||||
Entity,
|
||||
&'static mut Window,
|
||||
Option<&'static RawHandleWrapperHolder>,
|
||||
),
|
||||
F,
|
||||
>,
|
||||
EventWriter<'w, WindowCreated>,
|
||||
NonSendMut<'w, WinitWindows>,
|
||||
NonSendMut<'w, AccessKitAdapters>,
|
||||
ResMut<'w, WinitActionRequestHandlers>,
|
||||
Res<'w, AccessibilityRequested>,
|
||||
);
|
||||
|
|
771
crates/bevy_winit/src/state.rs
Normal file
771
crates/bevy_winit/src/state.rs
Normal file
|
@ -0,0 +1,771 @@
|
|||
use approx::relative_eq;
|
||||
use bevy_app::{App, AppExit, PluginsState};
|
||||
use bevy_ecs::change_detection::{DetectChanges, NonSendMut, Res};
|
||||
use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::event::{EventWriter, ManualEventReader};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::system::SystemState;
|
||||
use bevy_ecs::world::FromWorld;
|
||||
use bevy_input::{
|
||||
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
|
||||
touchpad::{TouchpadMagnify, TouchpadRotate},
|
||||
};
|
||||
use bevy_log::{error, trace, warn};
|
||||
use bevy_math::{ivec2, DVec2, Vec2};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use bevy_tasks::tick_global_task_pools_on_main_thread;
|
||||
use bevy_utils::Instant;
|
||||
use std::marker::PhantomData;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::event;
|
||||
use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
||||
use winit::window::WindowId;
|
||||
|
||||
#[allow(deprecated)]
|
||||
use bevy_window::{
|
||||
AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter,
|
||||
RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed,
|
||||
WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged,
|
||||
WindowThemeChanged,
|
||||
};
|
||||
#[cfg(target_os = "android")]
|
||||
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
||||
|
||||
use crate::accessibility::AccessKitAdapters;
|
||||
use crate::system::CachedWindow;
|
||||
use crate::{
|
||||
converters, create_windows, AppSendEvent, CreateWindowParams, UpdateMode, WinitEvent,
|
||||
WinitSettings, WinitWindows,
|
||||
};
|
||||
|
||||
/// Persistent state that is used to run the [`App`] according to the current
|
||||
/// [`UpdateMode`].
|
||||
struct WinitAppRunnerState<T: Event> {
|
||||
/// The running app.
|
||||
app: App,
|
||||
/// Exit value once the loop is finished.
|
||||
app_exit: Option<AppExit>,
|
||||
/// Current update mode of the app.
|
||||
update_mode: UpdateMode,
|
||||
/// Is `true` if a new [`WindowEvent`] event has been received since the last update.
|
||||
window_event_received: bool,
|
||||
/// Is `true` if a new [`DeviceEvent`] event has been received since the last update.
|
||||
device_event_received: bool,
|
||||
/// Is `true` if a new [`T`] event has been received since the last update.
|
||||
user_event_received: bool,
|
||||
/// Is `true` if the app has requested a redraw since the last update.
|
||||
redraw_requested: bool,
|
||||
/// Is `true` if enough time has elapsed since `last_update` to run another update.
|
||||
wait_elapsed: bool,
|
||||
/// Number of "forced" updates to trigger on application start
|
||||
startup_forced_updates: u32,
|
||||
|
||||
/// Current app lifecycle state.
|
||||
lifecycle: AppLifecycle,
|
||||
/// The previous app lifecycle state.
|
||||
previous_lifecycle: AppLifecycle,
|
||||
/// Winit events to send
|
||||
winit_events: Vec<WinitEvent>,
|
||||
_marker: PhantomData<T>,
|
||||
|
||||
event_writer_system_state: SystemState<(
|
||||
EventWriter<'static, WindowResized>,
|
||||
EventWriter<'static, WindowBackendScaleFactorChanged>,
|
||||
EventWriter<'static, WindowScaleFactorChanged>,
|
||||
NonSend<'static, WinitWindows>,
|
||||
Query<'static, 'static, (&'static mut Window, &'static mut CachedWindow)>,
|
||||
NonSendMut<'static, AccessKitAdapters>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<T: Event> WinitAppRunnerState<T> {
|
||||
fn new(mut app: App) -> Self {
|
||||
app.add_event::<T>();
|
||||
|
||||
let event_writer_system_state: SystemState<(
|
||||
EventWriter<WindowResized>,
|
||||
EventWriter<WindowBackendScaleFactorChanged>,
|
||||
EventWriter<WindowScaleFactorChanged>,
|
||||
NonSend<WinitWindows>,
|
||||
Query<(&mut Window, &mut CachedWindow)>,
|
||||
NonSendMut<AccessKitAdapters>,
|
||||
)> = 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,
|
||||
wait_elapsed: false,
|
||||
// 3 seems to be enough, 5 is a safe margin
|
||||
startup_forced_updates: 5,
|
||||
winit_events: Vec::new(),
|
||||
_marker: PhantomData,
|
||||
event_writer_system_state,
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn world_mut(&mut self) -> &mut World {
|
||||
self.app.world_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
|
||||
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||
if event_loop.exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = bevy_utils::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),
|
||||
..
|
||||
} => {
|
||||
// If the resume time is not after now, it means that at least the wait timeout
|
||||
// has elapsed.
|
||||
resume <= Instant::now()
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
// Mark the state as `WillResume`. This will let the schedule run one extra time
|
||||
// when actually resuming the app
|
||||
self.lifecycle = AppLifecycle::WillResume;
|
||||
}
|
||||
|
||||
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: T) {
|
||||
self.user_event_received = true;
|
||||
|
||||
self.world_mut().send_event(event);
|
||||
self.redraw_requested = true;
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
_event_loop: &ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
self.window_event_received = true;
|
||||
|
||||
let (
|
||||
mut window_resized,
|
||||
mut window_backend_scale_factor_changed,
|
||||
mut window_scale_factor_changed,
|
||||
winit_windows,
|
||||
mut windows,
|
||||
mut access_kit_adapters,
|
||||
) = self.event_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, _)) = windows.get_mut(window) else {
|
||||
warn!("Window {window:?} is missing `Window` component, skipping event {event:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
// Allow AccessKit to respond to `WindowEvent`s before they reach
|
||||
// the engine.
|
||||
if let Some(adapter) = access_kit_adapters.get_mut(&window) {
|
||||
if 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.winit_events.send(WindowCloseRequested { window }),
|
||||
WindowEvent::KeyboardInput { ref event, .. } => {
|
||||
if event.state.is_pressed() {
|
||||
if let Some(char) = &event.text {
|
||||
let char = char.clone();
|
||||
#[allow(deprecated)]
|
||||
self.winit_events.send(ReceivedCharacter { window, char });
|
||||
}
|
||||
}
|
||||
self.winit_events
|
||||
.send(converters::convert_keyboard_input(event, window));
|
||||
}
|
||||
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.winit_events.send(CursorMoved {
|
||||
window,
|
||||
position,
|
||||
delta,
|
||||
});
|
||||
}
|
||||
WindowEvent::CursorEntered { .. } => {
|
||||
self.winit_events.send(CursorEntered { window });
|
||||
}
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
win.set_physical_cursor_position(None);
|
||||
self.winit_events.send(CursorLeft { window });
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
self.winit_events.send(MouseButtonInput {
|
||||
button: converters::convert_mouse_button(button),
|
||||
state: converters::convert_element_state(state),
|
||||
window,
|
||||
});
|
||||
}
|
||||
WindowEvent::PinchGesture { delta, .. } => {
|
||||
self.winit_events.send(TouchpadMagnify(delta as f32));
|
||||
}
|
||||
WindowEvent::RotationGesture { delta, .. } => {
|
||||
self.winit_events.send(TouchpadRotate(delta));
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||
event::MouseScrollDelta::LineDelta(x, y) => {
|
||||
self.winit_events.send(MouseWheel {
|
||||
unit: MouseScrollUnit::Line,
|
||||
x,
|
||||
y,
|
||||
window,
|
||||
});
|
||||
}
|
||||
event::MouseScrollDelta::PixelDelta(p) => {
|
||||
self.winit_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.winit_events
|
||||
.send(converters::convert_touch_input(touch, location, window));
|
||||
}
|
||||
WindowEvent::Focused(focused) => {
|
||||
win.focused = focused;
|
||||
self.winit_events.send(WindowFocused { window, focused });
|
||||
}
|
||||
WindowEvent::Occluded(occluded) => {
|
||||
self.winit_events.send(WindowOccluded { window, occluded });
|
||||
}
|
||||
WindowEvent::DroppedFile(path_buf) => {
|
||||
self.winit_events
|
||||
.send(FileDragAndDrop::DroppedFile { window, path_buf });
|
||||
}
|
||||
WindowEvent::HoveredFile(path_buf) => {
|
||||
self.winit_events
|
||||
.send(FileDragAndDrop::HoveredFile { window, path_buf });
|
||||
}
|
||||
WindowEvent::HoveredFileCancelled => {
|
||||
self.winit_events
|
||||
.send(FileDragAndDrop::HoveredFileCanceled { window });
|
||||
}
|
||||
WindowEvent::Moved(position) => {
|
||||
let position = ivec2(position.x, position.y);
|
||||
win.position.set(position);
|
||||
self.winit_events.send(WindowMoved { window, position });
|
||||
}
|
||||
WindowEvent::Ime(event) => match event {
|
||||
event::Ime::Preedit(value, cursor) => {
|
||||
self.winit_events.send(Ime::Preedit {
|
||||
window,
|
||||
value,
|
||||
cursor,
|
||||
});
|
||||
}
|
||||
event::Ime::Commit(value) => {
|
||||
self.winit_events.send(Ime::Commit { window, value });
|
||||
}
|
||||
event::Ime::Enabled => {
|
||||
self.winit_events.send(Ime::Enabled { window });
|
||||
}
|
||||
event::Ime::Disabled => {
|
||||
self.winit_events.send(Ime::Disabled { window });
|
||||
}
|
||||
},
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
self.winit_events.send(WindowThemeChanged {
|
||||
window,
|
||||
theme: converters::convert_winit_theme(theme),
|
||||
});
|
||||
}
|
||||
WindowEvent::Destroyed => {
|
||||
self.winit_events.send(WindowDestroyed { window });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
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) {
|
||||
if window_component.is_changed() {
|
||||
cache.window = window_component.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.winit_events.send(MouseMotion { delta });
|
||||
}
|
||||
}
|
||||
|
||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||
// create any new windows
|
||||
// (even if app did not update, some may have been created by plugin setup)
|
||||
let mut create_window =
|
||||
SystemState::<CreateWindowParams<Added<Window>>>::from_world(self.world_mut());
|
||||
create_windows(event_loop, create_window.get_mut(self.world_mut()));
|
||||
create_window.apply(self.world_mut());
|
||||
|
||||
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
||||
|
||||
let mut focused_windows_state: SystemState<(Res<WinitSettings>, Query<(Entity, &Window)>)> =
|
||||
SystemState::new(self.world_mut());
|
||||
|
||||
if let Some(app_redraw_events) = self.world().get_resource::<Events<RequestRedraw>>() {
|
||||
if redraw_event_reader.read(app_redraw_events).last().is_some() {
|
||||
self.redraw_requested = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// Ensure that an update is triggered on the first iterations for app initialization
|
||||
should_update = true;
|
||||
}
|
||||
|
||||
if self.lifecycle == AppLifecycle::WillSuspend {
|
||||
self.lifecycle = AppLifecycle::Suspended;
|
||||
// Trigger one last update to enter the suspended state
|
||||
should_update = true;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// Remove the `RawHandleWrapper` from the primary window.
|
||||
// This will trigger the surface destruction.
|
||||
let mut query = self
|
||||
.world_mut()
|
||||
.query_filtered::<Entity, With<PrimaryWindow>>();
|
||||
let entity = query.single(&self.world());
|
||||
self.world_mut()
|
||||
.entity_mut(entity)
|
||||
.remove::<RawHandleWrapper>();
|
||||
}
|
||||
}
|
||||
|
||||
if self.lifecycle == AppLifecycle::WillResume {
|
||||
self.lifecycle = AppLifecycle::Running;
|
||||
// Trigger the update to enter the running state
|
||||
should_update = true;
|
||||
// Trigger the next redraw to refresh the screen immediately
|
||||
self.redraw_requested = true;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// Get windows that are cached but without raw handles. Those window were already created, but got their
|
||||
// handle wrapper removed when the app was suspended.
|
||||
let mut query = self.world_mut()
|
||||
.query_filtered::<(Entity, &Window), (With<CachedWindow>, Without<bevy_window::RawHandleWrapper>)>();
|
||||
if let Ok((entity, window)) = query.get_single(&self.world()) {
|
||||
let window = window.clone();
|
||||
|
||||
let mut create_window =
|
||||
SystemState::<CreateWindowParams>::from_world(self.world_mut());
|
||||
|
||||
let (
|
||||
..,
|
||||
mut winit_windows,
|
||||
mut adapters,
|
||||
mut handlers,
|
||||
accessibility_requested,
|
||||
) = create_window.get_mut(self.world_mut());
|
||||
|
||||
let winit_window = winit_windows.create_window(
|
||||
event_loop,
|
||||
entity,
|
||||
&window,
|
||||
&mut adapters,
|
||||
&mut handlers,
|
||||
&accessibility_requested,
|
||||
);
|
||||
|
||||
let wrapper = RawHandleWrapper::new(winit_window).unwrap();
|
||||
|
||||
self.world_mut().entity_mut(entity).insert(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notifies a lifecycle change
|
||||
if self.lifecycle != self.previous_lifecycle {
|
||||
self.previous_lifecycle = self.lifecycle;
|
||||
self.winit_events.send(self.lifecycle);
|
||||
}
|
||||
|
||||
// This is recorded before running app.update(), to run the next cycle after a correct timeout.
|
||||
// If the cycle takes more than the wait timeout, it will be re-executed immediately.
|
||||
let begin_frame_time = Instant::now();
|
||||
|
||||
if should_update {
|
||||
// Not redrawing, but the timeout elapsed.
|
||||
self.run_app_update();
|
||||
|
||||
// Running the app may have changed the WinitSettings resource, so we have to re-extract it.
|
||||
let (config, windows) = focused_windows_state.get(self.world());
|
||||
let focused = windows.iter().any(|(_, window)| window.focused);
|
||||
|
||||
update_mode = config.update_mode(focused);
|
||||
}
|
||||
|
||||
// The update mode could have been changed, so we need to redraw and force an update
|
||||
if update_mode != self.update_mode {
|
||||
// Trigger the next redraw since we're changing the update mode
|
||||
self.redraw_requested = true;
|
||||
// Consider the wait as elapsed since it could have been cancelled by a user event
|
||||
self.wait_elapsed = true;
|
||||
|
||||
self.update_mode = update_mode;
|
||||
}
|
||||
|
||||
match update_mode {
|
||||
UpdateMode::Continuous => {
|
||||
// per winit's docs on [Window::is_visible](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.is_visible),
|
||||
// we cannot use the visibility to drive rendering on these platforms
|
||||
// so we cannot discern whether to beneficially use `Poll` or not?
|
||||
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 winit_windows = self.world().non_send_resource::<WinitWindows>();
|
||||
let visible = 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the next redraw to refresh the screen immediately if waiting
|
||||
if let ControlFlow::Wait = event_loop.control_flow() {
|
||||
self.redraw_requested = true;
|
||||
}
|
||||
}
|
||||
UpdateMode::Reactive { wait, .. } => {
|
||||
// Set the next timeout, starting from the instant before running app.update() to avoid frame delays
|
||||
if let Some(next) = begin_frame_time.checked_add(wait) {
|
||||
if self.wait_elapsed {
|
||||
event_loop.set_control_flow(ControlFlow::WaitUntil(next));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.redraw_requested && self.lifecycle != AppLifecycle::Suspended {
|
||||
let winit_windows = self.world().non_send_resource::<WinitWindows>();
|
||||
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 suspended(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
// Mark the state as `WillSuspend`. This will let the schedule run one last time
|
||||
// before actually suspending to let the application react
|
||||
self.lifecycle = AppLifecycle::WillSuspend;
|
||||
}
|
||||
|
||||
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
let world = self.world_mut();
|
||||
world.clear_all();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Event> WinitAppRunnerState<T> {
|
||||
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_winit_events();
|
||||
|
||||
if self.app.plugins_state() == PluginsState::Cleaned {
|
||||
self.app.update();
|
||||
}
|
||||
}
|
||||
|
||||
fn forward_winit_events(&mut self) {
|
||||
let buffered_events = self.winit_events.drain(..).collect::<Vec<_>>();
|
||||
|
||||
if buffered_events.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let world = self.world_mut();
|
||||
|
||||
for winit_event in buffered_events.iter() {
|
||||
match winit_event.clone() {
|
||||
WinitEvent::AppLifecycle(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::CursorEntered(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::CursorLeft(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::CursorMoved(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::FileDragAndDrop(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::Ime(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::ReceivedCharacter(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::RequestRedraw(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowBackendScaleFactorChanged(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowCloseRequested(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowCreated(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowDestroyed(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowFocused(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowMoved(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowOccluded(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowResized(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowScaleFactorChanged(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::WindowThemeChanged(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::MouseButtonInput(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::MouseMotion(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::MouseWheel(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::TouchpadMagnify(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::TouchpadRotate(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::TouchInput(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
WinitEvent::KeyboardInput(e) => {
|
||||
world.send_event(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
world
|
||||
.resource_mut::<Events<WinitEvent>>()
|
||||
.send_batch(buffered_events);
|
||||
}
|
||||
}
|
||||
|
||||
/// The default [`App::runner`] for the [`WinitPlugin`] plugin.
|
||||
///
|
||||
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
|
||||
/// `EventLoop`.
|
||||
pub fn winit_runner<T: Event>(mut app: App) -> AppExit {
|
||||
if app.plugins_state() == PluginsState::Ready {
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
}
|
||||
|
||||
let event_loop = app
|
||||
.world_mut()
|
||||
.remove_non_send_resource::<EventLoop<T>>()
|
||||
.unwrap();
|
||||
|
||||
app.world_mut()
|
||||
.insert_non_send_resource(event_loop.create_proxy());
|
||||
|
||||
let mut runner_state = WinitAppRunnerState::new(app);
|
||||
|
||||
trace!("starting winit event loop");
|
||||
// TODO(clean): the winit docs mention using `spawn` instead of `run` on WASM.
|
||||
if let Err(err) = event_loop.run_app(&mut runner_state) {
|
||||
error!("winit event loop returned an error: {err}");
|
||||
}
|
||||
|
||||
// If everything is working correctly then the event loop only exits after it's sent an exit code.
|
||||
runner_state.app_exit.unwrap_or_else(|| {
|
||||
error!("Failed to receive a app exit code! This is a bug");
|
||||
AppExit::error()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn react_to_resize(
|
||||
window_entity: Entity,
|
||||
window: &mut Mut<'_, Window>,
|
||||
size: PhysicalSize<u32>,
|
||||
window_resized: &mut EventWriter<WindowResized>,
|
||||
) {
|
||||
window
|
||||
.resolution
|
||||
.set_physical_resolution(size.width, size.height);
|
||||
|
||||
window_resized.send(WindowResized {
|
||||
window: window_entity,
|
||||
width: window.width(),
|
||||
height: window.height(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn react_to_scale_factor_change(
|
||||
window_entity: Entity,
|
||||
window: &mut Mut<'_, Window>,
|
||||
scale_factor: f64,
|
||||
window_backend_scale_factor_changed: &mut EventWriter<WindowBackendScaleFactorChanged>,
|
||||
window_scale_factor_changed: &mut EventWriter<WindowScaleFactorChanged>,
|
||||
) {
|
||||
window.resolution.set_scale_factor(scale_factor as f32);
|
||||
|
||||
window_backend_scale_factor_changed.send(WindowBackendScaleFactorChanged {
|
||||
window: window_entity,
|
||||
scale_factor,
|
||||
});
|
||||
|
||||
let prior_factor = window.resolution.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.send(WindowScaleFactorChanged {
|
||||
window: window_entity,
|
||||
scale_factor,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -12,15 +12,14 @@ use bevy_window::{
|
|||
WindowMode, WindowResized,
|
||||
};
|
||||
|
||||
use winit::{
|
||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
||||
event_loop::EventLoopWindowTarget,
|
||||
};
|
||||
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
|
||||
use bevy_ecs::query::With;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
use crate::state::react_to_resize;
|
||||
use crate::{
|
||||
converters::{
|
||||
self, convert_enabled_buttons, convert_window_level, convert_window_theme,
|
||||
|
@ -36,7 +35,7 @@ use crate::{
|
|||
/// default values.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_windows<F: QueryFilter + 'static>(
|
||||
event_loop: &EventLoopWindowTarget<crate::UserEvent>,
|
||||
event_loop: &ActiveEventLoop,
|
||||
(
|
||||
mut commands,
|
||||
mut created_windows,
|
||||
|
@ -47,7 +46,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
|||
accessibility_requested,
|
||||
): SystemParamItem<CreateWindowParams<F>>,
|
||||
) {
|
||||
for (entity, mut window) in &mut created_windows {
|
||||
for (entity, mut window, handle_holder) in &mut created_windows {
|
||||
if winit_windows.get_window(entity).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
@ -80,7 +79,11 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
|||
});
|
||||
|
||||
if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) {
|
||||
commands.entity(entity).insert(handle_wrapper);
|
||||
let mut entity = commands.entity(entity);
|
||||
entity.insert(handle_wrapper.clone());
|
||||
if let Some(handle_holder) = handle_holder {
|
||||
*handle_holder.0.lock().unwrap() = Some(handle_wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -181,13 +184,46 @@ pub(crate) fn changed_windows(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if window.resolution != cache.window.resolution {
|
||||
let physical_size = PhysicalSize::new(
|
||||
window.resolution.physical_width(),
|
||||
window.resolution.physical_height(),
|
||||
let mut physical_size = winit_window.inner_size();
|
||||
|
||||
let cached_physical_size = PhysicalSize::new(
|
||||
cache.window.physical_width(),
|
||||
cache.window.physical_height(),
|
||||
);
|
||||
if let Some(size_now) = winit_window.request_inner_size(physical_size) {
|
||||
crate::react_to_resize(&mut window, size_now, &mut window_resized, entity);
|
||||
|
||||
let base_scale_factor = window.resolution.base_scale_factor();
|
||||
|
||||
// Note: this may be different from `winit`'s base scale factor if
|
||||
// `scale_factor_override` is set to Some(f32)
|
||||
let scale_factor = window.scale_factor();
|
||||
let cached_scale_factor = cache.window.scale_factor();
|
||||
|
||||
// Check and update `winit`'s physical size only if the window is not maximized
|
||||
if scale_factor != cached_scale_factor && !winit_window.is_maximized() {
|
||||
let logical_size =
|
||||
if let Some(cached_factor) = cache.window.resolution.scale_factor_override() {
|
||||
physical_size.to_logical::<f32>(cached_factor as f64)
|
||||
} else {
|
||||
physical_size.to_logical::<f32>(base_scale_factor as f64)
|
||||
};
|
||||
|
||||
// Scale factor changed, updating physical and logical size
|
||||
if let Some(forced_factor) = window.resolution.scale_factor_override() {
|
||||
// This window is overriding the OS-suggested DPI, so its physical size
|
||||
// should be set based on the overriding value. Its logical size already
|
||||
// incorporates any resize constraints.
|
||||
physical_size = logical_size.to_physical::<u32>(forced_factor as f64);
|
||||
} else {
|
||||
physical_size = logical_size.to_physical::<u32>(base_scale_factor as f64);
|
||||
}
|
||||
}
|
||||
|
||||
if physical_size != cached_physical_size {
|
||||
if let Some(new_physical_size) = winit_window.request_inner_size(physical_size) {
|
||||
react_to_resize(entity, &mut window, new_physical_size, &mut window_resized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +238,7 @@ pub(crate) fn changed_windows(
|
|||
}
|
||||
|
||||
if window.cursor.icon != cache.window.cursor.icon {
|
||||
winit_window.set_cursor_icon(converters::convert_cursor_icon(window.cursor.icon));
|
||||
winit_window.set_cursor(converters::convert_cursor_icon(window.cursor.icon));
|
||||
}
|
||||
|
||||
if window.cursor.grab_mode != cache.window.cursor.grab_mode {
|
||||
|
|
|
@ -18,9 +18,7 @@ impl WinitSettings {
|
|||
pub fn game() -> Self {
|
||||
WinitSettings {
|
||||
focused_mode: UpdateMode::Continuous,
|
||||
unfocused_mode: UpdateMode::ReactiveLowPower {
|
||||
wait: Duration::from_secs_f64(1.0 / 60.0), // 60Hz
|
||||
},
|
||||
unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs_f64(1.0 / 60.0)), // 60Hz,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,12 +30,8 @@ impl WinitSettings {
|
|||
/// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy.
|
||||
pub fn desktop_app() -> Self {
|
||||
WinitSettings {
|
||||
focused_mode: UpdateMode::Reactive {
|
||||
wait: Duration::from_secs(5),
|
||||
},
|
||||
unfocused_mode: UpdateMode::ReactiveLowPower {
|
||||
wait: Duration::from_secs(60),
|
||||
},
|
||||
focused_mode: UpdateMode::reactive(Duration::from_secs(5)),
|
||||
unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(60)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +66,7 @@ pub enum UpdateMode {
|
|||
/// [`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](`winit::event::WindowEvent`) or [raw input](`winit::event::DeviceEvent`)
|
||||
/// - new [window](`winit::event::WindowEvent`), [raw input](`winit::event::DeviceEvent`), or custom
|
||||
/// events have appeared
|
||||
/// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy)
|
||||
Reactive {
|
||||
|
@ -81,23 +75,38 @@ pub enum UpdateMode {
|
|||
/// **Note:** This has no upper limit.
|
||||
/// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].
|
||||
wait: Duration,
|
||||
},
|
||||
/// The [`App`](bevy_app::App) will update in response to the following, until an
|
||||
/// [`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
|
||||
/// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy)
|
||||
///
|
||||
/// **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).
|
||||
/// Use this mode if, for example, you only want your app to update when the mouse cursor is
|
||||
/// moving over a window, not just moving in general. This can greatly reduce power consumption.
|
||||
ReactiveLowPower {
|
||||
/// The approximate time from the start of one update to the next.
|
||||
///
|
||||
/// **Note:** This has no upper limit.
|
||||
/// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].
|
||||
wait: Duration,
|
||||
/// Reacts to device events, that will wake up the loop if it's in a wait wtate
|
||||
react_to_device_events: bool,
|
||||
/// Reacts to user events, that will wake up the loop if it's in a wait wtate
|
||||
react_to_user_events: bool,
|
||||
/// Reacts to window events, that will wake up the loop if it's in a wait wtate
|
||||
react_to_window_events: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl UpdateMode {
|
||||
/// Reactive mode, will update the app for any kind of event
|
||||
pub fn reactive(wait: Duration) -> Self {
|
||||
Self::Reactive {
|
||||
wait,
|
||||
react_to_device_events: true,
|
||||
react_to_user_events: true,
|
||||
react_to_window_events: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Low power mode
|
||||
///
|
||||
/// Unlike [`Reactive`](`UpdateMode::reactive()`), this will ignore events that
|
||||
/// don't come from interacting with a window, like [`MouseMotion`](winit::event::DeviceEvent::MouseMotion).
|
||||
/// Use this if, for example, you only want your app to update when the mouse cursor is
|
||||
/// moving over a window, not just moving in general. This can greatly reduce power consumption.
|
||||
pub fn reactive_low_power(wait: Duration) -> Self {
|
||||
Self::Reactive {
|
||||
wait,
|
||||
react_to_device_events: false,
|
||||
react_to_user_events: true,
|
||||
react_to_window_events: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#![allow(deprecated)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_input::keyboard::KeyboardInput;
|
||||
use bevy_input::touch::TouchInput;
|
||||
|
@ -13,9 +12,9 @@ use bevy_reflect::Reflect;
|
|||
#[cfg(feature = "serialize")]
|
||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
use bevy_window::{
|
||||
ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime,
|
||||
ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested,
|
||||
WindowCreated, WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized,
|
||||
AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter,
|
||||
RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated,
|
||||
WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized,
|
||||
WindowScaleFactorChanged, WindowThemeChanged,
|
||||
};
|
||||
|
||||
|
@ -33,7 +32,7 @@ use bevy_window::{
|
|||
reflect(Serialize, Deserialize)
|
||||
)]
|
||||
pub enum WinitEvent {
|
||||
ApplicationLifetime(ApplicationLifetime),
|
||||
AppLifecycle(AppLifecycle),
|
||||
CursorEntered(CursorEntered),
|
||||
CursorLeft(CursorLeft),
|
||||
CursorMoved(CursorMoved),
|
||||
|
@ -64,9 +63,9 @@ pub enum WinitEvent {
|
|||
KeyboardInput(KeyboardInput),
|
||||
}
|
||||
|
||||
impl From<ApplicationLifetime> for WinitEvent {
|
||||
fn from(e: ApplicationLifetime) -> Self {
|
||||
Self::ApplicationLifetime(e)
|
||||
impl From<AppLifecycle> for WinitEvent {
|
||||
fn from(e: AppLifecycle) -> Self {
|
||||
Self::AppLifecycle(e)
|
||||
}
|
||||
}
|
||||
impl From<CursorEntered> for WinitEvent {
|
||||
|
@ -189,92 +188,3 @@ impl From<KeyboardInput> for WinitEvent {
|
|||
Self::KeyboardInput(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards buffered [`WinitEvent`] events to the app.
|
||||
pub(crate) fn forward_winit_events(buffered_events: &mut Vec<WinitEvent>, app: &mut App) {
|
||||
if buffered_events.is_empty() {
|
||||
return;
|
||||
}
|
||||
for winit_event in buffered_events.iter() {
|
||||
match winit_event.clone() {
|
||||
WinitEvent::ApplicationLifetime(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::CursorEntered(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::CursorLeft(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::CursorMoved(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::FileDragAndDrop(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::Ime(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::ReceivedCharacter(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::RequestRedraw(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowBackendScaleFactorChanged(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowCloseRequested(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowCreated(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowDestroyed(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowFocused(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowMoved(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowOccluded(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowResized(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowScaleFactorChanged(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::WindowThemeChanged(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::MouseButtonInput(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::MouseMotion(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::MouseWheel(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::TouchpadMagnify(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::TouchpadRotate(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::TouchInput(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
WinitEvent::KeyboardInput(e) => {
|
||||
app.world_mut().send_event(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
app.world_mut()
|
||||
.resource_mut::<Events<WinitEvent>>()
|
||||
.send_batch(buffered_events.drain(..));
|
||||
}
|
||||
|
|
|
@ -9,11 +9,15 @@ use bevy_window::{
|
|||
|
||||
use winit::{
|
||||
dpi::{LogicalSize, PhysicalPosition},
|
||||
monitor::MonitorHandle,
|
||||
event_loop::ActiveEventLoop,
|
||||
monitor::{MonitorHandle, VideoModeHandle},
|
||||
window::{CursorGrabMode as WinitCursorGrabMode, Fullscreen, Window as WinitWindow, WindowId},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
accessibility::{prepare_accessibility_for_window, AccessKitAdapters, WinitActionHandlers},
|
||||
accessibility::{
|
||||
prepare_accessibility_for_window, AccessKitAdapters, WinitActionRequestHandlers,
|
||||
},
|
||||
converters::{convert_enabled_buttons, convert_window_level, convert_window_theme},
|
||||
};
|
||||
|
||||
|
@ -22,11 +26,11 @@ use crate::{
|
|||
#[derive(Debug, Default)]
|
||||
pub struct WinitWindows {
|
||||
/// Stores [`winit`] windows by window identifier.
|
||||
pub windows: HashMap<winit::window::WindowId, WindowWrapper<winit::window::Window>>,
|
||||
pub windows: HashMap<WindowId, WindowWrapper<WinitWindow>>,
|
||||
/// Maps entities to `winit` window identifiers.
|
||||
pub entity_to_winit: EntityHashMap<winit::window::WindowId>,
|
||||
pub entity_to_winit: EntityHashMap<WindowId>,
|
||||
/// Maps `winit` window identifiers to entities.
|
||||
pub winit_to_entity: HashMap<winit::window::WindowId, Entity>,
|
||||
pub winit_to_entity: HashMap<WindowId, Entity>,
|
||||
// Many `winit` window functions (e.g. `set_window_icon`) can only be called on the main thread.
|
||||
// If they're called on other threads, the program might hang. This marker indicates that this
|
||||
// type is not thread-safe and will be `!Send` and `!Sync`.
|
||||
|
@ -37,23 +41,22 @@ impl WinitWindows {
|
|||
/// Creates a `winit` window and associates it with our entity.
|
||||
pub fn create_window(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::EventLoopWindowTarget<crate::UserEvent>,
|
||||
event_loop: &ActiveEventLoop,
|
||||
entity: Entity,
|
||||
window: &Window,
|
||||
adapters: &mut AccessKitAdapters,
|
||||
handlers: &mut WinitActionHandlers,
|
||||
handlers: &mut WinitActionRequestHandlers,
|
||||
accessibility_requested: &AccessibilityRequested,
|
||||
) -> &WindowWrapper<winit::window::Window> {
|
||||
let mut winit_window_builder = winit::window::WindowBuilder::new();
|
||||
) -> &WindowWrapper<WinitWindow> {
|
||||
let mut winit_window_attributes = WinitWindow::default_attributes();
|
||||
|
||||
// Due to a UIA limitation, winit windows need to be invisible for the
|
||||
// AccessKit adapter is initialized.
|
||||
winit_window_builder = winit_window_builder.with_visible(false);
|
||||
winit_window_attributes = winit_window_attributes.with_visible(false);
|
||||
|
||||
winit_window_builder = match window.mode {
|
||||
WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some(
|
||||
winit::window::Fullscreen::Borderless(event_loop.primary_monitor()),
|
||||
)),
|
||||
winit_window_attributes = match window.mode {
|
||||
WindowMode::BorderlessFullscreen => winit_window_attributes
|
||||
.with_fullscreen(Some(Fullscreen::Borderless(event_loop.primary_monitor()))),
|
||||
mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => {
|
||||
if let Some(primary_monitor) = event_loop.primary_monitor() {
|
||||
let videomode = match mode {
|
||||
|
@ -66,11 +69,10 @@ impl WinitWindows {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
winit_window_builder
|
||||
.with_fullscreen(Some(winit::window::Fullscreen::Exclusive(videomode)))
|
||||
winit_window_attributes.with_fullscreen(Some(Fullscreen::Exclusive(videomode)))
|
||||
} else {
|
||||
warn!("Could not determine primary monitor, ignoring exclusive fullscreen request for window {:?}", window.title);
|
||||
winit_window_builder
|
||||
winit_window_attributes
|
||||
}
|
||||
}
|
||||
WindowMode::Windowed => {
|
||||
|
@ -81,19 +83,20 @@ impl WinitWindows {
|
|||
event_loop.primary_monitor(),
|
||||
None,
|
||||
) {
|
||||
winit_window_builder = winit_window_builder.with_position(position);
|
||||
winit_window_attributes = winit_window_attributes.with_position(position);
|
||||
}
|
||||
|
||||
let logical_size = LogicalSize::new(window.width(), window.height());
|
||||
if let Some(sf) = window.resolution.scale_factor_override() {
|
||||
winit_window_builder.with_inner_size(logical_size.to_physical::<f64>(sf.into()))
|
||||
winit_window_attributes
|
||||
.with_inner_size(logical_size.to_physical::<f64>(sf.into()))
|
||||
} else {
|
||||
winit_window_builder.with_inner_size(logical_size)
|
||||
winit_window_attributes.with_inner_size(logical_size)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
winit_window_builder = winit_window_builder
|
||||
winit_window_attributes = winit_window_attributes
|
||||
.with_window_level(convert_window_level(window.window_level))
|
||||
.with_theme(window.window_theme.map(convert_window_theme))
|
||||
.with_resizable(window.resizable)
|
||||
|
@ -104,8 +107,9 @@ impl WinitWindows {
|
|||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use winit::platform::windows::WindowBuilderExtWindows;
|
||||
winit_window_builder = winit_window_builder.with_skip_taskbar(window.skip_taskbar);
|
||||
use winit::platform::windows::WindowAttributesExtWindows;
|
||||
winit_window_attributes =
|
||||
winit_window_attributes.with_skip_taskbar(window.skip_taskbar);
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
|
@ -128,11 +132,12 @@ impl WinitWindows {
|
|||
)
|
||||
))]
|
||||
{
|
||||
winit_window_builder = winit::platform::wayland::WindowBuilderExtWayland::with_name(
|
||||
winit_window_builder,
|
||||
name.clone(),
|
||||
"",
|
||||
);
|
||||
winit_window_attributes =
|
||||
winit::platform::wayland::WindowAttributesExtWayland::with_name(
|
||||
winit_window_attributes,
|
||||
name.clone(),
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
|
@ -146,17 +151,17 @@ impl WinitWindows {
|
|||
)
|
||||
))]
|
||||
{
|
||||
winit_window_builder = winit::platform::x11::WindowBuilderExtX11::with_name(
|
||||
winit_window_builder,
|
||||
winit_window_attributes = winit::platform::x11::WindowAttributesExtX11::with_name(
|
||||
winit_window_attributes,
|
||||
name.clone(),
|
||||
"",
|
||||
);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
winit_window_builder =
|
||||
winit::platform::windows::WindowBuilderExtWindows::with_class_name(
|
||||
winit_window_builder,
|
||||
winit_window_attributes =
|
||||
winit::platform::windows::WindowAttributesExtWindows::with_class_name(
|
||||
winit_window_attributes,
|
||||
name.clone(),
|
||||
);
|
||||
}
|
||||
|
@ -172,22 +177,22 @@ impl WinitWindows {
|
|||
height: constraints.max_height,
|
||||
};
|
||||
|
||||
let winit_window_builder =
|
||||
let winit_window_attributes =
|
||||
if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
|
||||
winit_window_builder
|
||||
winit_window_attributes
|
||||
.with_min_inner_size(min_inner_size)
|
||||
.with_max_inner_size(max_inner_size)
|
||||
} else {
|
||||
winit_window_builder.with_min_inner_size(min_inner_size)
|
||||
winit_window_attributes.with_min_inner_size(min_inner_size)
|
||||
};
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut winit_window_builder = winit_window_builder.with_title(window.title.as_str());
|
||||
let mut winit_window_attributes = winit_window_attributes.with_title(window.title.as_str());
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use wasm_bindgen::JsCast;
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
use winit::platform::web::WindowAttributesExtWebSys;
|
||||
|
||||
if let Some(selector) = &window.canvas {
|
||||
let window = web_sys::window().unwrap();
|
||||
|
@ -197,18 +202,18 @@ impl WinitWindows {
|
|||
.expect("Cannot query for canvas element.");
|
||||
if let Some(canvas) = canvas {
|
||||
let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
|
||||
winit_window_builder = winit_window_builder.with_canvas(canvas);
|
||||
winit_window_attributes = winit_window_attributes.with_canvas(canvas);
|
||||
} else {
|
||||
panic!("Cannot find element: {}.", selector);
|
||||
}
|
||||
}
|
||||
|
||||
winit_window_builder =
|
||||
winit_window_builder.with_prevent_default(window.prevent_default_event_handling);
|
||||
winit_window_builder = winit_window_builder.with_append(true);
|
||||
winit_window_attributes =
|
||||
winit_window_attributes.with_prevent_default(window.prevent_default_event_handling);
|
||||
winit_window_attributes = winit_window_attributes.with_append(true);
|
||||
}
|
||||
|
||||
let winit_window = winit_window_builder.build(event_loop).unwrap();
|
||||
let winit_window = event_loop.create_window(winit_window_attributes).unwrap();
|
||||
let name = window.title.clone();
|
||||
prepare_accessibility_for_window(
|
||||
&winit_window,
|
||||
|
@ -247,7 +252,7 @@ impl WinitWindows {
|
|||
}
|
||||
|
||||
/// Get the winit window that is associated with our entity.
|
||||
pub fn get_window(&self, entity: Entity) -> Option<&WindowWrapper<winit::window::Window>> {
|
||||
pub fn get_window(&self, entity: Entity) -> Option<&WindowWrapper<WinitWindow>> {
|
||||
self.entity_to_winit
|
||||
.get(&entity)
|
||||
.and_then(|winit_id| self.windows.get(winit_id))
|
||||
|
@ -256,17 +261,14 @@ impl WinitWindows {
|
|||
/// Get the entity associated with the winit window id.
|
||||
///
|
||||
/// This is mostly just an intermediary step between us and winit.
|
||||
pub fn get_window_entity(&self, winit_id: winit::window::WindowId) -> Option<Entity> {
|
||||
pub fn get_window_entity(&self, winit_id: WindowId) -> Option<Entity> {
|
||||
self.winit_to_entity.get(&winit_id).cloned()
|
||||
}
|
||||
|
||||
/// Remove a window from winit.
|
||||
///
|
||||
/// This should mostly just be called when the window is closing.
|
||||
pub fn remove_window(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
) -> Option<WindowWrapper<winit::window::Window>> {
|
||||
pub fn remove_window(&mut self, entity: Entity) -> Option<WindowWrapper<WinitWindow>> {
|
||||
let winit_id = self.entity_to_winit.remove(&entity)?;
|
||||
self.winit_to_entity.remove(&winit_id);
|
||||
self.windows.remove(&winit_id)
|
||||
|
@ -276,11 +278,7 @@ impl WinitWindows {
|
|||
/// Gets the "best" video mode which fits the given dimensions.
|
||||
///
|
||||
/// The heuristic for "best" prioritizes width, height, and refresh rate in that order.
|
||||
pub fn get_fitting_videomode(
|
||||
monitor: &MonitorHandle,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> winit::monitor::VideoMode {
|
||||
pub fn get_fitting_videomode(monitor: &MonitorHandle, width: u32, height: u32) -> VideoModeHandle {
|
||||
let mut modes = monitor.video_modes().collect::<Vec<_>>();
|
||||
|
||||
fn abs_diff(a: u32, b: u32) -> u32 {
|
||||
|
@ -308,10 +306,10 @@ pub fn get_fitting_videomode(
|
|||
modes.first().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Gets the "best" videomode from a monitor.
|
||||
/// Gets the "best" video-mode handle from a monitor.
|
||||
///
|
||||
/// The heuristic for "best" prioritizes width, height, and refresh rate in that order.
|
||||
pub fn get_best_videomode(monitor: &MonitorHandle) -> winit::monitor::VideoMode {
|
||||
pub fn get_best_videomode(monitor: &MonitorHandle) -> VideoModeHandle {
|
||||
let mut modes = monitor.video_modes().collect::<Vec<_>>();
|
||||
modes.sort_by(|a, b| {
|
||||
use std::cmp::Ordering::*;
|
||||
|
@ -329,15 +327,15 @@ pub fn get_best_videomode(monitor: &MonitorHandle) -> winit::monitor::VideoMode
|
|||
modes.first().unwrap().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn attempt_grab(winit_window: &winit::window::Window, grab_mode: CursorGrabMode) {
|
||||
pub(crate) fn attempt_grab(winit_window: &WinitWindow, grab_mode: CursorGrabMode) {
|
||||
let grab_result = match grab_mode {
|
||||
CursorGrabMode::None => winit_window.set_cursor_grab(winit::window::CursorGrabMode::None),
|
||||
CursorGrabMode::None => winit_window.set_cursor_grab(WinitCursorGrabMode::None),
|
||||
CursorGrabMode::Confined => winit_window
|
||||
.set_cursor_grab(winit::window::CursorGrabMode::Confined)
|
||||
.or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Locked)),
|
||||
.set_cursor_grab(WinitCursorGrabMode::Confined)
|
||||
.or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Locked)),
|
||||
CursorGrabMode::Locked => winit_window
|
||||
.set_cursor_grab(winit::window::CursorGrabMode::Locked)
|
||||
.or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Confined)),
|
||||
.set_cursor_grab(WinitCursorGrabMode::Locked)
|
||||
.or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Confined)),
|
||||
};
|
||||
|
||||
if let Err(err) = grab_result {
|
||||
|
|
|
@ -459,6 +459,7 @@ Example | Description
|
|||
Example | Description
|
||||
--- | ---
|
||||
[Clear Color](../examples/window/clear_color.rs) | Creates a solid color window
|
||||
[Custom User Event](../examples/window/custom_user_event.rs) | Handles custom user events within the event loop
|
||||
[Low Power](../examples/window/low_power.rs) | Demonstrates settings to reduce power use for bevy applications
|
||||
[Multiple Windows](../examples/window/multiple_windows.rs) | Demonstrates creating multiple windows, and rendering to them
|
||||
[Scale Factor Override](../examples/window/scale_factor_override.rs) | Illustrates how to customize the default window settings
|
||||
|
|
|
@ -4,7 +4,7 @@ use bevy::{
|
|||
color::palettes::basic::*,
|
||||
input::touch::TouchPhase,
|
||||
prelude::*,
|
||||
window::{ApplicationLifetime, WindowMode},
|
||||
window::{AppLifecycle, WindowMode},
|
||||
};
|
||||
|
||||
// the `bevy_main` proc_macro generates the required boilerplate for iOS and Android
|
||||
|
@ -166,14 +166,18 @@ fn setup_music(asset_server: Res<AssetServer>, mut commands: Commands) {
|
|||
// Pause audio when app goes into background and resume when it returns.
|
||||
// This is handled by the OS on iOS, but not on Android.
|
||||
fn handle_lifetime(
|
||||
mut lifetime_events: EventReader<ApplicationLifetime>,
|
||||
mut lifecycle_events: EventReader<AppLifecycle>,
|
||||
music_controller: Query<&AudioSink>,
|
||||
) {
|
||||
for event in lifetime_events.read() {
|
||||
let Ok(music_controller) = music_controller.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
for event in lifecycle_events.read() {
|
||||
match event {
|
||||
ApplicationLifetime::Suspended => music_controller.single().pause(),
|
||||
ApplicationLifetime::Resumed => music_controller.single().play(),
|
||||
ApplicationLifetime::Started => (),
|
||||
AppLifecycle::Idle | AppLifecycle::WillSuspend | AppLifecycle::WillResume => {}
|
||||
AppLifecycle::Suspended => music_controller.pause(),
|
||||
AppLifecycle::Running => music_controller.play(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
122
examples/window/custom_user_event.rs
Normal file
122
examples/window/custom_user_event.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
//! Shows how to create a custom event that can be handled by `winit`'s event loop.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::winit::{EventLoopProxy, WakeUp, WinitPlugin};
|
||||
use std::fmt::Formatter;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[derive(Default, Debug, Event)]
|
||||
enum CustomEvent {
|
||||
#[default]
|
||||
WakeUp,
|
||||
Key(char),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CustomEvent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::WakeUp => write!(f, "Wake up"),
|
||||
Self::Key(ch) => write!(f, "Key: {ch}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static EVENT_LOOP_PROXY: OnceLock<EventLoopProxy<CustomEvent>> = OnceLock::new();
|
||||
|
||||
fn main() {
|
||||
let winit_plugin = WinitPlugin::<CustomEvent>::default();
|
||||
|
||||
App::new()
|
||||
.add_plugins(
|
||||
DefaultPlugins
|
||||
.build()
|
||||
// Only one event type can be handled at once
|
||||
// so we must disable the default event type
|
||||
.disable::<WinitPlugin<WakeUp>>()
|
||||
.add(winit_plugin),
|
||||
)
|
||||
.add_systems(
|
||||
Startup,
|
||||
(
|
||||
setup,
|
||||
expose_event_loop_proxy,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm::setup_js_closure,
|
||||
),
|
||||
)
|
||||
.add_systems(Update, (send_event, handle_event))
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
}
|
||||
|
||||
fn send_event(input: Res<ButtonInput<KeyCode>>) {
|
||||
let Some(event_loop_proxy) = EVENT_LOOP_PROXY.get() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::WakeUp);
|
||||
}
|
||||
|
||||
// This simulates sending a custom event through an external thread.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if input.just_pressed(KeyCode::KeyE) {
|
||||
let handler = std::thread::spawn(|| {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::Key('e'));
|
||||
});
|
||||
|
||||
handler.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn expose_event_loop_proxy(event_loop_proxy: NonSend<EventLoopProxy<CustomEvent>>) {
|
||||
EVENT_LOOP_PROXY.set((*event_loop_proxy).clone()).unwrap();
|
||||
}
|
||||
|
||||
fn handle_event(mut events: EventReader<CustomEvent>) {
|
||||
for evt in events.read() {
|
||||
info!("Received event: {evt:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Since the [`EventLoopProxy`] can be exposed to the javascript environment, it can
|
||||
/// be used to send events inside the loop, to be handled by a system or simply to wake up
|
||||
/// the loop if that's currently waiting for a timeout or a user event.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(crate) mod wasm {
|
||||
use crate::{CustomEvent, EVENT_LOOP_PROXY};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::KeyboardEvent;
|
||||
|
||||
pub(crate) fn setup_js_closure() {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
|
||||
let closure = Closure::wrap(Box::new(move |event: KeyboardEvent| {
|
||||
let key = event.key();
|
||||
if key == "e" {
|
||||
send_custom_event('e').unwrap();
|
||||
}
|
||||
}) as Box<dyn FnMut(KeyboardEvent)>);
|
||||
|
||||
document
|
||||
.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
fn send_custom_event(ch: char) -> Result<(), String> {
|
||||
if let Some(proxy) = EVENT_LOOP_PROXY.get() {
|
||||
proxy
|
||||
.send_event(CustomEvent::Key(ch))
|
||||
.map_err(|_| "Failed to send event".to_string())
|
||||
} else {
|
||||
Err("Event loop proxy not found".to_string())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,10 +3,12 @@
|
|||
//! This is useful for making desktop applications, or any other program that doesn't need to be
|
||||
//! running the event loop non-stop.
|
||||
|
||||
use bevy::window::WindowResolution;
|
||||
use bevy::winit::WakeUp;
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
utils::Duration,
|
||||
window::{PresentMode, RequestRedraw, WindowPlugin},
|
||||
window::{PresentMode, WindowPlugin},
|
||||
winit::{EventLoopProxy, WinitSettings},
|
||||
};
|
||||
|
||||
|
@ -19,15 +21,14 @@ fn main() {
|
|||
// You can also customize update behavior with the fields of [`WinitSettings`]
|
||||
.insert_resource(WinitSettings {
|
||||
focused_mode: bevy::winit::UpdateMode::Continuous,
|
||||
unfocused_mode: bevy::winit::UpdateMode::ReactiveLowPower {
|
||||
wait: Duration::from_millis(10),
|
||||
},
|
||||
unfocused_mode: bevy::winit::UpdateMode::reactive_low_power(Duration::from_millis(10)),
|
||||
})
|
||||
.insert_resource(ExampleMode::Game)
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
// Turn off vsync to maximize CPU/GPU usage
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
resolution: WindowResolution::new(800., 640.).with_scale_factor_override(1.),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
|
@ -56,7 +57,7 @@ enum ExampleMode {
|
|||
fn update_winit(
|
||||
mode: Res<ExampleMode>,
|
||||
mut winit_config: ResMut<WinitSettings>,
|
||||
event_loop_proxy: NonSend<EventLoopProxy>,
|
||||
event_loop_proxy: NonSend<EventLoopProxy<WakeUp>>,
|
||||
) {
|
||||
use ExampleMode::*;
|
||||
*winit_config = match *mode {
|
||||
|
@ -78,7 +79,10 @@ fn update_winit(
|
|||
// (e.g. the mouse hovers over a visible part of the out of focus window), a
|
||||
// [`RequestRedraw`] event is received, or one minute has passed without the app
|
||||
// updating.
|
||||
WinitSettings::desktop_app()
|
||||
WinitSettings {
|
||||
focused_mode: bevy::winit::UpdateMode::reactive(Duration::from_secs(1)),
|
||||
unfocused_mode: bevy::winit::UpdateMode::reactive_low_power(Duration::from_secs(5)),
|
||||
}
|
||||
}
|
||||
ApplicationWithRedraw => {
|
||||
// Sending a `RequestRedraw` event is useful when you want the app to update the next
|
||||
|
@ -87,7 +91,7 @@ fn update_winit(
|
|||
// when there are no inputs, so you send redraw requests while the animation is playing.
|
||||
// Note that in this example the RequestRedraw winit event will make the app run in the same
|
||||
// way as continuous
|
||||
let _ = event_loop_proxy.send_event(RequestRedraw);
|
||||
let _ = event_loop_proxy.send_event(WakeUp);
|
||||
WinitSettings::desktop_app()
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
use bevy::{prelude::*, window::WindowResolution};
|
||||
|
||||
#[derive(Component)]
|
||||
struct CustomText;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
|
@ -39,7 +42,7 @@ fn setup(mut commands: Commands) {
|
|||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(200.0),
|
||||
width: Val::Px(300.0),
|
||||
height: Val::Percent(100.0),
|
||||
border: UiRect::all(Val::Px(2.0)),
|
||||
..default()
|
||||
|
@ -48,7 +51,8 @@ fn setup(mut commands: Commands) {
|
|||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn(
|
||||
parent.spawn((
|
||||
CustomText,
|
||||
TextBundle::from_section(
|
||||
"Example text",
|
||||
TextStyle {
|
||||
|
@ -60,19 +64,32 @@ fn setup(mut commands: Commands) {
|
|||
align_self: AlignSelf::FlexEnd,
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the title of the window to the current override
|
||||
fn display_override(mut windows: Query<&mut Window>) {
|
||||
fn display_override(
|
||||
mut windows: Query<&mut Window>,
|
||||
mut custom_text: Query<&mut Text, With<CustomText>>,
|
||||
) {
|
||||
let mut window = windows.single_mut();
|
||||
|
||||
window.title = format!(
|
||||
"Scale override: {:?}",
|
||||
window.resolution.scale_factor_override()
|
||||
let text = format!(
|
||||
"Scale factor: {:.1} {}",
|
||||
window.scale_factor(),
|
||||
if window.resolution.scale_factor_override().is_some() {
|
||||
"(overridden)"
|
||||
} else {
|
||||
"(default)"
|
||||
}
|
||||
);
|
||||
|
||||
window.title.clone_from(&text);
|
||||
|
||||
let mut custom_text = custom_text.single_mut();
|
||||
custom_text.sections[0].value = text;
|
||||
}
|
||||
|
||||
/// This system toggles scale factor overrides when enter is pressed
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs
|
||||
index 63d79b1d2..83ed56293 100644
|
||||
--- a/crates/bevy_winit/src/lib.rs
|
||||
+++ b/crates/bevy_winit/src/lib.rs
|
||||
@@ -429,6 +429,12 @@ fn handle_winit_event(
|
||||
diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs
|
||||
index df0aab42d..6e28a6e9c 100644
|
||||
--- a/crates/bevy_winit/src/state.rs
|
||||
+++ b/crates/bevy_winit/src/state.rs
|
||||
@@ -208,6 +208,12 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
|
||||
}
|
||||
}
|
||||
|
||||
runner_state.window_event_received = true;
|
||||
|
||||
+ window_resized.send(WindowResized {
|
||||
+ window,
|
||||
+ width: win.width(),
|
||||
+ height: win.height(),
|
||||
+ });
|
||||
+ window_resized.send(WindowResized {
|
||||
+ window,
|
||||
+ width: win.width(),
|
||||
+ height: win.height(),
|
||||
+ });
|
||||
+
|
||||
match event {
|
||||
WindowEvent::Resized(size) => {
|
||||
react_to_resize(&mut win, size, &mut window_resized, window);
|
||||
match event {
|
||||
WindowEvent::Resized(size) => {
|
||||
react_to_resize(window, &mut win, size, &mut window_resized);
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
diff --git a/crates/bevy_winit/src/winit_config.rs b/crates/bevy_winit/src/winit_config.rs
|
||||
index f2cb424ec..e68e01de0 100644
|
||||
index 104384086..6e3c8dd83 100644
|
||||
--- a/crates/bevy_winit/src/winit_config.rs
|
||||
+++ b/crates/bevy_winit/src/winit_config.rs
|
||||
@@ -31,14 +31,7 @@ impl WinitSettings {
|
||||
@@ -29,10 +29,7 @@ impl WinitSettings {
|
||||
///
|
||||
/// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy.
|
||||
pub fn desktop_app() -> Self {
|
||||
- WinitSettings {
|
||||
- focused_mode: UpdateMode::Reactive {
|
||||
- wait: Duration::from_secs(5),
|
||||
- },
|
||||
- unfocused_mode: UpdateMode::ReactiveLowPower {
|
||||
- wait: Duration::from_secs(60),
|
||||
- },
|
||||
- focused_mode: UpdateMode::reactive(Duration::from_secs(5)),
|
||||
- unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(60)),
|
||||
- }
|
||||
+ Self::default()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue