mirror of
https://github.com/bevyengine/bevy
synced 2024-12-01 08:59:11 +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"
|
thiserror = "1.0"
|
||||||
event-listener = "5.3.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]]
|
[[example]]
|
||||||
name = "hello_world"
|
name = "hello_world"
|
||||||
path = "examples/hello_world.rs"
|
path = "examples/hello_world.rs"
|
||||||
|
@ -2836,6 +2840,17 @@ description = "Creates a solid color window"
|
||||||
category = "Window"
|
category = "Window"
|
||||||
wasm = true
|
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]]
|
[[example]]
|
||||||
name = "low_power"
|
name = "low_power"
|
||||||
path = "examples/window/low_power.rs"
|
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_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
|
||||||
|
|
||||||
accesskit = "0.12"
|
accesskit = "0.14"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -69,7 +69,7 @@ impl PluginGroup for DefaultPlugins {
|
||||||
|
|
||||||
#[cfg(feature = "bevy_winit")]
|
#[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")]
|
#[cfg(feature = "bevy_render")]
|
||||||
|
|
|
@ -58,7 +58,7 @@ use bevy_utils::prelude::default;
|
||||||
pub use extract_param::Extract;
|
pub use extract_param::Extract;
|
||||||
|
|
||||||
use bevy_hierarchy::ValidParentCheckPlugin;
|
use bevy_hierarchy::ValidParentCheckPlugin;
|
||||||
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
use bevy_window::{PrimaryWindow, RawHandleWrapperHolder};
|
||||||
use extract_resource::ExtractResourcePlugin;
|
use extract_resource::ExtractResourcePlugin;
|
||||||
use globals::GlobalsPlugin;
|
use globals::GlobalsPlugin;
|
||||||
use render_asset::RenderAssetBytesPerFrame;
|
use render_asset::RenderAssetBytesPerFrame;
|
||||||
|
@ -268,10 +268,9 @@ impl Plugin for RenderPlugin {
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut system_state: SystemState<
|
let mut system_state: SystemState<
|
||||||
Query<&RawHandleWrapper, With<PrimaryWindow>>,
|
Query<&RawHandleWrapperHolder, With<PrimaryWindow>>,
|
||||||
> = SystemState::new(app.world_mut());
|
> = SystemState::new(app.world_mut());
|
||||||
let primary_window = system_state.get(app.world()).get_single().ok().cloned();
|
let primary_window = system_state.get(app.world()).get_single().ok().cloned();
|
||||||
|
|
||||||
let settings = render_creation.clone();
|
let settings = render_creation.clone();
|
||||||
let async_renderer = async move {
|
let async_renderer = async move {
|
||||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
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.
|
// SAFETY: Plugins should be set up on the main thread.
|
||||||
let surface = primary_window.map(|wrapper| unsafe {
|
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();
|
let handle = wrapper.get_handle();
|
||||||
|
Some(
|
||||||
instance
|
instance
|
||||||
.create_surface(handle)
|
.create_surface(handle)
|
||||||
.expect("Failed to create wgpu surface")
|
.expect("Failed to create wgpu surface"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let request_adapter_options = wgpu::RequestAdapterOptions {
|
let request_adapter_options = wgpu::RequestAdapterOptions {
|
||||||
|
|
|
@ -22,7 +22,7 @@ concurrent-queue = { version = "2.0.0", optional = true }
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
web-time = { version = "0.2" }
|
web-time = { version = "1.1" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -14,7 +14,7 @@ detailed_trace = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ahash = "0.8.7"
|
ahash = "0.8.7"
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
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"] }
|
hashbrown = { version = "0.14", features = ["serde"] }
|
||||||
bevy_utils_proc_macros = { version = "0.14.0-dev", path = "macros" }
|
bevy_utils_proc_macros = { version = "0.14.0-dev", path = "macros" }
|
||||||
thread_local = "1.0"
|
thread_local = "1.0"
|
||||||
|
|
|
@ -388,13 +388,28 @@ pub struct WindowThemeChanged {
|
||||||
derive(serde::Serialize, serde::Deserialize),
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub enum ApplicationLifetime {
|
pub enum AppLifecycle {
|
||||||
/// The application just started.
|
/// The application is not started yet.
|
||||||
Started,
|
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.
|
/// The application was suspended.
|
||||||
///
|
|
||||||
/// On Android, applications have one frame to react to this event before being paused in the background.
|
|
||||||
Suspended,
|
Suspended,
|
||||||
/// The application was resumed.
|
/// The application is going to be resumed.
|
||||||
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
|
//! The [`WindowPlugin`] sets up some global window-related parameters and
|
||||||
//! is part of the [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html).
|
//! is part of the [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html).
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use bevy_a11y::Focus;
|
use bevy_a11y::Focus;
|
||||||
|
|
||||||
mod cursor;
|
mod cursor;
|
||||||
|
@ -106,13 +108,16 @@ impl Plugin for WindowPlugin {
|
||||||
.add_event::<FileDragAndDrop>()
|
.add_event::<FileDragAndDrop>()
|
||||||
.add_event::<WindowMoved>()
|
.add_event::<WindowMoved>()
|
||||||
.add_event::<WindowThemeChanged>()
|
.add_event::<WindowThemeChanged>()
|
||||||
.add_event::<ApplicationLifetime>();
|
.add_event::<AppLifecycle>();
|
||||||
|
|
||||||
if let Some(primary_window) = &self.primary_window {
|
if let Some(primary_window) = &self.primary_window {
|
||||||
let initial_focus = app
|
let initial_focus = app
|
||||||
.world_mut()
|
.world_mut()
|
||||||
.spawn(primary_window.clone())
|
.spawn(primary_window.clone())
|
||||||
.insert(PrimaryWindow)
|
.insert((
|
||||||
|
PrimaryWindow,
|
||||||
|
RawHandleWrapperHolder(Arc::new(Mutex::new(None))),
|
||||||
|
))
|
||||||
.id();
|
.id();
|
||||||
if let Some(mut focus) = app.world_mut().get_resource_mut::<Focus>() {
|
if let Some(mut focus) = app.world_mut().get_resource_mut::<Focus>() {
|
||||||
**focus = Some(initial_focus);
|
**focus = Some(initial_focus);
|
||||||
|
@ -153,7 +158,7 @@ impl Plugin for WindowPlugin {
|
||||||
.register_type::<FileDragAndDrop>()
|
.register_type::<FileDragAndDrop>()
|
||||||
.register_type::<WindowMoved>()
|
.register_type::<WindowMoved>()
|
||||||
.register_type::<WindowThemeChanged>()
|
.register_type::<WindowThemeChanged>()
|
||||||
.register_type::<ApplicationLifetime>();
|
.register_type::<AppLifecycle>();
|
||||||
|
|
||||||
// Register window descriptor and related types
|
// Register window descriptor and related types
|
||||||
app.register_type::<Window>()
|
app.register_type::<Window>()
|
||||||
|
|
|
@ -5,7 +5,12 @@ use raw_window_handle::{
|
||||||
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
|
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
|
||||||
RawWindowHandle, WindowHandle,
|
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.
|
/// A wrapper over a window.
|
||||||
///
|
///
|
||||||
|
@ -116,3 +121,7 @@ impl HasDisplayHandle for ThreadLockedRawWindowHandleWrapper {
|
||||||
Ok(unsafe { DisplayHandle::borrow_raw(self.0.display_handle) })
|
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 {
|
impl WindowResolution {
|
||||||
/// Creates a new [`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 {
|
Self {
|
||||||
physical_width: logical_width as u32,
|
physical_width: physical_width as u32,
|
||||||
physical_height: logical_height as u32,
|
physical_height: physical_height as u32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,9 +783,7 @@ impl WindowResolution {
|
||||||
/// Set the window's scale factor, this may get overridden by the backend.
|
/// Set the window's scale factor, this may get overridden by the backend.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_scale_factor(&mut self, scale_factor: f32) {
|
pub fn set_scale_factor(&mut self, scale_factor: f32) {
|
||||||
let (width, height) = (self.width(), self.height());
|
|
||||||
self.scale_factor = scale_factor;
|
self.scale_factor = scale_factor;
|
||||||
self.set(width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the window's scale factor, this will be used over what the backend decides.
|
/// 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.
|
/// size is not within the limits.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_scale_factor_override(&mut self, scale_factor_override: Option<f32>) {
|
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.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
|
# other
|
||||||
# feature rwh_06 refers to window_raw_handle@v0.6
|
# feature rwh_06 refers to window_raw_handle@v0.6
|
||||||
winit = { version = "0.29", default-features = false, features = ["rwh_06"] }
|
winit = { version = "0.30", default-features = false, features = ["rwh_06"] }
|
||||||
accesskit_winit = { version = "0.17", default-features = false, features = [
|
accesskit_winit = { version = "0.20", default-features = false, features = [
|
||||||
"rwh_06",
|
"rwh_06",
|
||||||
] }
|
] }
|
||||||
approx = { version = "0.5", default-features = false }
|
approx = { version = "0.5", default-features = false }
|
||||||
|
@ -42,7 +42,7 @@ raw-window-handle = "0.6"
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[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",
|
"android-native-activity",
|
||||||
"rwh_06",
|
"rwh_06",
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -6,10 +6,9 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use accesskit_winit::Adapter;
|
use accesskit_winit::Adapter;
|
||||||
|
use bevy_a11y::accesskit::{ActivationHandler, DeactivationHandler, Node};
|
||||||
use bevy_a11y::{
|
use bevy_a11y::{
|
||||||
accesskit::{
|
accesskit::{ActionHandler, ActionRequest, NodeBuilder, NodeId, Role, Tree, TreeUpdate},
|
||||||
ActionHandler, ActionRequest, NodeBuilder, NodeClassSet, NodeId, Role, Tree, TreeUpdate,
|
|
||||||
},
|
|
||||||
AccessibilityNode, AccessibilityRequested, AccessibilitySystem, Focus,
|
AccessibilityNode, AccessibilityRequested, AccessibilitySystem, Focus,
|
||||||
};
|
};
|
||||||
use bevy_a11y::{ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates};
|
use bevy_a11y::{ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates};
|
||||||
|
@ -20,7 +19,7 @@ use bevy_ecs::{
|
||||||
prelude::{DetectChanges, Entity, EventReader, EventWriter},
|
prelude::{DetectChanges, Entity, EventReader, EventWriter},
|
||||||
query::With,
|
query::With,
|
||||||
schedule::IntoSystemConfigs,
|
schedule::IntoSystemConfigs,
|
||||||
system::{NonSend, NonSendMut, Query, Res, ResMut, Resource},
|
system::{NonSendMut, Query, Res, ResMut, Resource},
|
||||||
};
|
};
|
||||||
use bevy_hierarchy::{Children, Parent};
|
use bevy_hierarchy::{Children, Parent};
|
||||||
use bevy_window::{PrimaryWindow, Window, WindowClosed};
|
use bevy_window::{PrimaryWindow, Window, WindowClosed};
|
||||||
|
@ -29,13 +28,78 @@ use bevy_window::{PrimaryWindow, Window, WindowClosed};
|
||||||
#[derive(Default, Deref, DerefMut)]
|
#[derive(Default, Deref, DerefMut)]
|
||||||
pub struct AccessKitAdapters(pub EntityHashMap<Adapter>);
|
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)]
|
#[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.
|
/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel.
|
||||||
#[derive(Clone, Default, Deref, DerefMut)]
|
#[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 {
|
impl ActionHandler for WinitActionHandler {
|
||||||
fn do_action(&mut self, request: ActionRequest) {
|
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.
|
/// Prepares accessibility for a winit window.
|
||||||
pub(crate) fn prepare_accessibility_for_window(
|
pub(crate) fn prepare_accessibility_for_window(
|
||||||
winit_window: &winit::window::Window,
|
winit_window: &winit::window::Window,
|
||||||
|
@ -51,43 +127,39 @@ pub(crate) fn prepare_accessibility_for_window(
|
||||||
name: String,
|
name: String,
|
||||||
accessibility_requested: AccessibilityRequested,
|
accessibility_requested: AccessibilityRequested,
|
||||||
adapters: &mut AccessKitAdapters,
|
adapters: &mut AccessKitAdapters,
|
||||||
handlers: &mut WinitActionHandlers,
|
handlers: &mut WinitActionRequestHandlers,
|
||||||
) {
|
) {
|
||||||
let mut root_builder = NodeBuilder::new(Role::Window);
|
let state = AccessKitState::new(name, entity, accessibility_requested);
|
||||||
root_builder.set_name(name.into_boxed_str());
|
let activation_handler = WinitActivationHandler::new(Arc::clone(&state));
|
||||||
let root = root_builder.build(&mut NodeClassSet::lock_global());
|
|
||||||
|
|
||||||
let accesskit_window_id = NodeId(entity.to_bits());
|
let action_request_handler = WinitActionRequestHandler::new();
|
||||||
let handler = WinitActionHandler::default();
|
let action_handler = WinitActionHandler::new(Arc::clone(&action_request_handler));
|
||||||
let adapter = Adapter::with_action_handler(
|
let deactivation_handler = WinitDeactivationHandler;
|
||||||
|
|
||||||
|
let adapter = Adapter::with_direct_handlers(
|
||||||
winit_window,
|
winit_window,
|
||||||
move || {
|
activation_handler,
|
||||||
accessibility_requested.set(true);
|
action_handler,
|
||||||
TreeUpdate {
|
deactivation_handler,
|
||||||
nodes: vec![(accesskit_window_id, root)],
|
|
||||||
tree: Some(Tree::new(accesskit_window_id)),
|
|
||||||
focus: accesskit_window_id,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Box::new(handler.clone()),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
adapters.insert(entity, adapter);
|
adapters.insert(entity, adapter);
|
||||||
handlers.insert(entity, handler);
|
handlers.insert(entity, action_request_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_closed(
|
fn window_closed(
|
||||||
mut adapters: NonSendMut<AccessKitAdapters>,
|
mut adapters: NonSendMut<AccessKitAdapters>,
|
||||||
mut receivers: ResMut<WinitActionHandlers>,
|
mut handlers: ResMut<WinitActionRequestHandlers>,
|
||||||
mut events: EventReader<WindowClosed>,
|
mut events: EventReader<WindowClosed>,
|
||||||
) {
|
) {
|
||||||
for WindowClosed { window, .. } in events.read() {
|
for WindowClosed { window, .. } in events.read() {
|
||||||
adapters.remove(window);
|
adapters.remove(window);
|
||||||
receivers.remove(window);
|
handlers.remove(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_receivers(
|
fn poll_receivers(
|
||||||
handlers: Res<WinitActionHandlers>,
|
handlers: Res<WinitActionRequestHandlers>,
|
||||||
mut actions: EventWriter<ActionRequestWrapper>,
|
mut actions: EventWriter<ActionRequestWrapper>,
|
||||||
) {
|
) {
|
||||||
for (_id, handler) in handlers.iter() {
|
for (_id, handler) in handlers.iter() {
|
||||||
|
@ -106,7 +178,7 @@ fn should_update_accessibility_nodes(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_accessibility_nodes(
|
fn update_accessibility_nodes(
|
||||||
adapters: NonSend<AccessKitAdapters>,
|
mut adapters: NonSendMut<AccessKitAdapters>,
|
||||||
focus: Res<Focus>,
|
focus: Res<Focus>,
|
||||||
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
|
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
|
||||||
nodes: Query<(
|
nodes: Query<(
|
||||||
|
@ -120,7 +192,7 @@ fn update_accessibility_nodes(
|
||||||
let Ok((primary_window_id, primary_window)) = primary_window.get_single() else {
|
let Ok((primary_window_id, primary_window)) = primary_window.get_single() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(adapter) = adapters.get(&primary_window_id) else {
|
let Some(adapter) = adapters.get_mut(&primary_window_id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if focus.is_changed() || !nodes.is_empty() {
|
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);
|
queue_node_for_update(entity, parent, &node_entities, &mut window_children);
|
||||||
add_children_nodes(children, &node_entities, &mut node);
|
add_children_nodes(children, &node_entities, &mut node);
|
||||||
let node_id = NodeId(entity.to_bits());
|
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));
|
to_update.push((node_id, node));
|
||||||
}
|
}
|
||||||
let mut window_node = NodeBuilder::new(Role::Window);
|
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_name(title.into_boxed_str());
|
||||||
}
|
}
|
||||||
window_node.set_children(window_children);
|
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 node_id = NodeId(primary_window_id.to_bits());
|
||||||
let window_update = (node_id, window_node);
|
let window_update = (node_id, window_node);
|
||||||
to_update.insert(0, window_update);
|
to_update.insert(0, window_update);
|
||||||
|
@ -214,7 +286,7 @@ pub struct AccessKitPlugin;
|
||||||
impl Plugin for AccessKitPlugin {
|
impl Plugin for AccessKitPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_non_send_resource::<AccessKitAdapters>()
|
app.init_non_send_resource::<AccessKitAdapters>()
|
||||||
.init_resource::<WinitActionHandlers>()
|
.init_resource::<WinitActionRequestHandlers>()
|
||||||
.add_event::<ActionRequestWrapper>()
|
.add_event::<ActionRequestWrapper>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
|
|
|
@ -12,60 +12,33 @@
|
||||||
//! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`].
|
//! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`].
|
||||||
//! See `winit_runner` for details.
|
//! See `winit_runner` for details.
|
||||||
|
|
||||||
pub mod accessibility;
|
use bevy_window::RawHandleWrapperHolder;
|
||||||
mod converters;
|
use std::marker::PhantomData;
|
||||||
mod system;
|
use winit::event_loop::EventLoop;
|
||||||
mod winit_config;
|
#[cfg(target_os = "android")]
|
||||||
pub mod winit_event;
|
pub use winit::platform::android::activity as android_activity;
|
||||||
mod winit_windows;
|
|
||||||
|
|
||||||
use std::sync::mpsc::{sync_channel, SyncSender};
|
|
||||||
|
|
||||||
use approx::relative_eq;
|
|
||||||
use bevy_a11y::AccessibilityRequested;
|
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;
|
pub use system::create_windows;
|
||||||
use system::{changed_windows, despawn_windows, CachedWindow};
|
use system::{changed_windows, despawn_windows};
|
||||||
use winit::dpi::{LogicalSize, PhysicalSize};
|
|
||||||
pub use winit_config::*;
|
pub use winit_config::*;
|
||||||
pub use winit_event::*;
|
pub use winit_event::*;
|
||||||
pub use winit_windows::*;
|
pub use winit_windows::*;
|
||||||
|
|
||||||
use bevy_app::{App, AppExit, Last, Plugin, PluginsState};
|
use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers};
|
||||||
use bevy_ecs::event::ManualEventReader;
|
use crate::state::winit_runner;
|
||||||
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};
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
pub mod accessibility;
|
||||||
pub use winit::platform::android::activity as android_activity;
|
mod converters;
|
||||||
|
mod state;
|
||||||
use winit::event::StartCause;
|
mod system;
|
||||||
use winit::{
|
mod winit_config;
|
||||||
event::{self, DeviceEvent, Event, WindowEvent},
|
pub mod winit_event;
|
||||||
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget},
|
mod winit_windows;
|
||||||
};
|
|
||||||
|
|
||||||
use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionHandlers};
|
|
||||||
|
|
||||||
use crate::converters::convert_winit_theme;
|
|
||||||
|
|
||||||
/// [`AndroidApp`] provides an interface to query the application state as well as monitor events
|
/// [`AndroidApp`] provides an interface to query the application state as well as monitor events
|
||||||
/// (for example lifecycle and input 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
|
/// 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
|
/// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to
|
||||||
/// receive window and input events from the OS.
|
/// 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)]
|
#[derive(Default)]
|
||||||
pub struct WinitPlugin {
|
pub struct WinitPlugin<T: Event = WakeUp> {
|
||||||
/// Allows the window (and the event loop) to be created on any thread
|
/// Allows the window (and the event loop) to be created on any thread
|
||||||
/// instead of only the main thread.
|
/// instead of only the main thread.
|
||||||
///
|
///
|
||||||
|
@ -91,18 +67,23 @@ pub struct WinitPlugin {
|
||||||
/// Only works on Linux (X11/Wayland) and Windows.
|
/// Only works on Linux (X11/Wayland) and Windows.
|
||||||
/// This field is ignored on other platforms.
|
/// This field is ignored on other platforms.
|
||||||
pub run_on_any_thread: bool,
|
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) {
|
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.
|
// linux check is needed because x11 might be enabled on other platforms.
|
||||||
#[cfg(all(target_os = "linux", feature = "x11"))]
|
#[cfg(all(target_os = "linux", feature = "x11"))]
|
||||||
{
|
{
|
||||||
use winit::platform::x11::EventLoopBuilderExtX11;
|
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
|
// 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
|
// which runs a Bevy app without requiring the Bevy app to need to reside on
|
||||||
// the main thread, which can be problematic.
|
// the main thread, which can be problematic.
|
||||||
|
@ -132,7 +113,7 @@ impl Plugin for WinitPlugin {
|
||||||
app.init_non_send_resource::<WinitWindows>()
|
app.init_non_send_resource::<WinitWindows>()
|
||||||
.init_resource::<WinitSettings>()
|
.init_resource::<WinitSettings>()
|
||||||
.add_event::<WinitEvent>()
|
.add_event::<WinitEvent>()
|
||||||
.set_runner(winit_runner)
|
.set_runner(winit_runner::<T>)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Last,
|
Last,
|
||||||
(
|
(
|
||||||
|
@ -150,691 +131,50 @@ impl Plugin for WinitPlugin {
|
||||||
.build()
|
.build()
|
||||||
.expect("Failed to build event loop");
|
.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
|
// `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.
|
// be inserted as a resource here to pass it onto the runner.
|
||||||
app.insert_non_send_resource(event_loop);
|
app.insert_non_send_resource(event_loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait AppSendEvent {
|
/// The default event that can be used to wake the window loop
|
||||||
fn send(&mut self, event: impl Into<WinitEvent>);
|
/// Wakes up the loop if in wait state
|
||||||
}
|
#[derive(Debug, Default, Clone, Copy, Event)]
|
||||||
impl AppSendEvent for Vec<WinitEvent> {
|
pub struct WakeUp;
|
||||||
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 [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`].
|
/// 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.
|
/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
|
||||||
///
|
///
|
||||||
/// Use `NonSend<EventLoopProxy>` to receive this resource.
|
/// 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;
|
trait AppSendEvent {
|
||||||
|
fn send(&mut self, event: impl Into<WinitEvent>);
|
||||||
/// 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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments /* TODO: probs can reduce # of args */)]
|
impl AppSendEvent for Vec<WinitEvent> {
|
||||||
fn handle_winit_event(
|
fn send(&mut self, event: impl Into<WinitEvent>) {
|
||||||
app: &mut App,
|
self.push(Into::<WinitEvent>::into(event));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn react_to_resize(
|
/// The parameters of the [`create_windows`] system.
|
||||||
win: &mut Mut<'_, Window>,
|
pub type CreateWindowParams<'w, 's, F = ()> = (
|
||||||
size: winit::dpi::PhysicalSize<u32>,
|
Commands<'w, 's>,
|
||||||
window_resized: &mut EventWriter<WindowResized>,
|
Query<
|
||||||
window: Entity,
|
'w,
|
||||||
) {
|
's,
|
||||||
win.resolution
|
(
|
||||||
.set_physical_resolution(size.width, size.height);
|
Entity,
|
||||||
|
&'static mut Window,
|
||||||
window_resized.send(WindowResized {
|
Option<&'static RawHandleWrapperHolder>,
|
||||||
window,
|
),
|
||||||
width: win.width(),
|
F,
|
||||||
height: win.height(),
|
>,
|
||||||
});
|
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,
|
WindowMode, WindowResized,
|
||||||
};
|
};
|
||||||
|
|
||||||
use winit::{
|
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
use winit::event_loop::ActiveEventLoop;
|
||||||
event_loop::EventLoopWindowTarget,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bevy_ecs::query::With;
|
use bevy_ecs::query::With;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use winit::platform::web::WindowExtWebSys;
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
|
||||||
|
use crate::state::react_to_resize;
|
||||||
use crate::{
|
use crate::{
|
||||||
converters::{
|
converters::{
|
||||||
self, convert_enabled_buttons, convert_window_level, convert_window_theme,
|
self, convert_enabled_buttons, convert_window_level, convert_window_theme,
|
||||||
|
@ -36,7 +35,7 @@ use crate::{
|
||||||
/// default values.
|
/// default values.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn create_windows<F: QueryFilter + 'static>(
|
pub fn create_windows<F: QueryFilter + 'static>(
|
||||||
event_loop: &EventLoopWindowTarget<crate::UserEvent>,
|
event_loop: &ActiveEventLoop,
|
||||||
(
|
(
|
||||||
mut commands,
|
mut commands,
|
||||||
mut created_windows,
|
mut created_windows,
|
||||||
|
@ -47,7 +46,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
||||||
accessibility_requested,
|
accessibility_requested,
|
||||||
): SystemParamItem<CreateWindowParams<F>>,
|
): 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() {
|
if winit_windows.get_window(entity).is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -80,7 +79,11 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) {
|
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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
@ -181,13 +184,46 @@ pub(crate) fn changed_windows(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if window.resolution != cache.window.resolution {
|
if window.resolution != cache.window.resolution {
|
||||||
let physical_size = PhysicalSize::new(
|
let mut physical_size = winit_window.inner_size();
|
||||||
window.resolution.physical_width(),
|
|
||||||
window.resolution.physical_height(),
|
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 {
|
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 {
|
if window.cursor.grab_mode != cache.window.cursor.grab_mode {
|
||||||
|
|
|
@ -18,9 +18,7 @@ impl WinitSettings {
|
||||||
pub fn game() -> Self {
|
pub fn game() -> Self {
|
||||||
WinitSettings {
|
WinitSettings {
|
||||||
focused_mode: UpdateMode::Continuous,
|
focused_mode: UpdateMode::Continuous,
|
||||||
unfocused_mode: UpdateMode::ReactiveLowPower {
|
unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs_f64(1.0 / 60.0)), // 60Hz,
|
||||||
wait: 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.
|
/// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy.
|
||||||
pub fn desktop_app() -> Self {
|
pub fn desktop_app() -> Self {
|
||||||
WinitSettings {
|
WinitSettings {
|
||||||
focused_mode: UpdateMode::Reactive {
|
focused_mode: UpdateMode::reactive(Duration::from_secs(5)),
|
||||||
wait: Duration::from_secs(5),
|
unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(60)),
|
||||||
},
|
|
||||||
unfocused_mode: UpdateMode::ReactiveLowPower {
|
|
||||||
wait: Duration::from_secs(60),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +66,7 @@ pub enum UpdateMode {
|
||||||
/// [`AppExit`](bevy_app::AppExit) event appears:
|
/// [`AppExit`](bevy_app::AppExit) event appears:
|
||||||
/// - `wait` time has elapsed since the previous update
|
/// - `wait` time has elapsed since the previous update
|
||||||
/// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw)
|
/// - 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
|
/// events have appeared
|
||||||
/// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy)
|
/// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy)
|
||||||
Reactive {
|
Reactive {
|
||||||
|
@ -81,23 +75,38 @@ pub enum UpdateMode {
|
||||||
/// **Note:** This has no upper limit.
|
/// **Note:** This has no upper limit.
|
||||||
/// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].
|
/// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].
|
||||||
wait: Duration,
|
wait: Duration,
|
||||||
},
|
/// Reacts to device events, that will wake up the loop if it's in a wait wtate
|
||||||
/// The [`App`](bevy_app::App) will update in response to the following, until an
|
react_to_device_events: bool,
|
||||||
/// [`AppExit`](bevy_app::AppExit) event appears:
|
/// Reacts to user events, that will wake up the loop if it's in a wait wtate
|
||||||
/// - `wait` time has elapsed since the previous update
|
react_to_user_events: bool,
|
||||||
/// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw)
|
/// Reacts to window events, that will wake up the loop if it's in a wait wtate
|
||||||
/// - new [window events](`winit::event::WindowEvent`) have appeared
|
react_to_window_events: bool,
|
||||||
/// - 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,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(deprecated)]
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use bevy_app::App;
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_input::keyboard::KeyboardInput;
|
use bevy_input::keyboard::KeyboardInput;
|
||||||
use bevy_input::touch::TouchInput;
|
use bevy_input::touch::TouchInput;
|
||||||
|
@ -13,9 +12,9 @@ use bevy_reflect::Reflect;
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||||
use bevy_window::{
|
use bevy_window::{
|
||||||
ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime,
|
AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter,
|
||||||
ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested,
|
RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated,
|
||||||
WindowCreated, WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized,
|
WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized,
|
||||||
WindowScaleFactorChanged, WindowThemeChanged,
|
WindowScaleFactorChanged, WindowThemeChanged,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,7 +32,7 @@ use bevy_window::{
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub enum WinitEvent {
|
pub enum WinitEvent {
|
||||||
ApplicationLifetime(ApplicationLifetime),
|
AppLifecycle(AppLifecycle),
|
||||||
CursorEntered(CursorEntered),
|
CursorEntered(CursorEntered),
|
||||||
CursorLeft(CursorLeft),
|
CursorLeft(CursorLeft),
|
||||||
CursorMoved(CursorMoved),
|
CursorMoved(CursorMoved),
|
||||||
|
@ -64,9 +63,9 @@ pub enum WinitEvent {
|
||||||
KeyboardInput(KeyboardInput),
|
KeyboardInput(KeyboardInput),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ApplicationLifetime> for WinitEvent {
|
impl From<AppLifecycle> for WinitEvent {
|
||||||
fn from(e: ApplicationLifetime) -> Self {
|
fn from(e: AppLifecycle) -> Self {
|
||||||
Self::ApplicationLifetime(e)
|
Self::AppLifecycle(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<CursorEntered> for WinitEvent {
|
impl From<CursorEntered> for WinitEvent {
|
||||||
|
@ -189,92 +188,3 @@ impl From<KeyboardInput> for WinitEvent {
|
||||||
Self::KeyboardInput(e)
|
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::{
|
use winit::{
|
||||||
dpi::{LogicalSize, PhysicalPosition},
|
dpi::{LogicalSize, PhysicalPosition},
|
||||||
monitor::MonitorHandle,
|
event_loop::ActiveEventLoop,
|
||||||
|
monitor::{MonitorHandle, VideoModeHandle},
|
||||||
|
window::{CursorGrabMode as WinitCursorGrabMode, Fullscreen, Window as WinitWindow, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
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},
|
converters::{convert_enabled_buttons, convert_window_level, convert_window_theme},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,11 +26,11 @@ use crate::{
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct WinitWindows {
|
pub struct WinitWindows {
|
||||||
/// Stores [`winit`] windows by window identifier.
|
/// 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.
|
/// 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.
|
/// 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.
|
// 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
|
// 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`.
|
// 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.
|
/// Creates a `winit` window and associates it with our entity.
|
||||||
pub fn create_window(
|
pub fn create_window(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &winit::event_loop::EventLoopWindowTarget<crate::UserEvent>,
|
event_loop: &ActiveEventLoop,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
window: &Window,
|
window: &Window,
|
||||||
adapters: &mut AccessKitAdapters,
|
adapters: &mut AccessKitAdapters,
|
||||||
handlers: &mut WinitActionHandlers,
|
handlers: &mut WinitActionRequestHandlers,
|
||||||
accessibility_requested: &AccessibilityRequested,
|
accessibility_requested: &AccessibilityRequested,
|
||||||
) -> &WindowWrapper<winit::window::Window> {
|
) -> &WindowWrapper<WinitWindow> {
|
||||||
let mut winit_window_builder = winit::window::WindowBuilder::new();
|
let mut winit_window_attributes = WinitWindow::default_attributes();
|
||||||
|
|
||||||
// Due to a UIA limitation, winit windows need to be invisible for the
|
// Due to a UIA limitation, winit windows need to be invisible for the
|
||||||
// AccessKit adapter is initialized.
|
// 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 {
|
winit_window_attributes = match window.mode {
|
||||||
WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some(
|
WindowMode::BorderlessFullscreen => winit_window_attributes
|
||||||
winit::window::Fullscreen::Borderless(event_loop.primary_monitor()),
|
.with_fullscreen(Some(Fullscreen::Borderless(event_loop.primary_monitor()))),
|
||||||
)),
|
|
||||||
mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => {
|
mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => {
|
||||||
if let Some(primary_monitor) = event_loop.primary_monitor() {
|
if let Some(primary_monitor) = event_loop.primary_monitor() {
|
||||||
let videomode = match mode {
|
let videomode = match mode {
|
||||||
|
@ -66,11 +69,10 @@ impl WinitWindows {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
winit_window_builder
|
winit_window_attributes.with_fullscreen(Some(Fullscreen::Exclusive(videomode)))
|
||||||
.with_fullscreen(Some(winit::window::Fullscreen::Exclusive(videomode)))
|
|
||||||
} else {
|
} else {
|
||||||
warn!("Could not determine primary monitor, ignoring exclusive fullscreen request for window {:?}", window.title);
|
warn!("Could not determine primary monitor, ignoring exclusive fullscreen request for window {:?}", window.title);
|
||||||
winit_window_builder
|
winit_window_attributes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowMode::Windowed => {
|
WindowMode::Windowed => {
|
||||||
|
@ -81,19 +83,20 @@ impl WinitWindows {
|
||||||
event_loop.primary_monitor(),
|
event_loop.primary_monitor(),
|
||||||
None,
|
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());
|
let logical_size = LogicalSize::new(window.width(), window.height());
|
||||||
if let Some(sf) = window.resolution.scale_factor_override() {
|
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 {
|
} 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_window_level(convert_window_level(window.window_level))
|
||||||
.with_theme(window.window_theme.map(convert_window_theme))
|
.with_theme(window.window_theme.map(convert_window_theme))
|
||||||
.with_resizable(window.resizable)
|
.with_resizable(window.resizable)
|
||||||
|
@ -104,8 +107,9 @@ impl WinitWindows {
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
use winit::platform::windows::WindowBuilderExtWindows;
|
use winit::platform::windows::WindowAttributesExtWindows;
|
||||||
winit_window_builder = winit_window_builder.with_skip_taskbar(window.skip_taskbar);
|
winit_window_attributes =
|
||||||
|
winit_window_attributes.with_skip_taskbar(window.skip_taskbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
|
@ -128,8 +132,9 @@ impl WinitWindows {
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
winit_window_builder = winit::platform::wayland::WindowBuilderExtWayland::with_name(
|
winit_window_attributes =
|
||||||
winit_window_builder,
|
winit::platform::wayland::WindowAttributesExtWayland::with_name(
|
||||||
|
winit_window_attributes,
|
||||||
name.clone(),
|
name.clone(),
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
@ -146,17 +151,17 @@ impl WinitWindows {
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
winit_window_builder = winit::platform::x11::WindowBuilderExtX11::with_name(
|
winit_window_attributes = winit::platform::x11::WindowAttributesExtX11::with_name(
|
||||||
winit_window_builder,
|
winit_window_attributes,
|
||||||
name.clone(),
|
name.clone(),
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
winit_window_builder =
|
winit_window_attributes =
|
||||||
winit::platform::windows::WindowBuilderExtWindows::with_class_name(
|
winit::platform::windows::WindowAttributesExtWindows::with_class_name(
|
||||||
winit_window_builder,
|
winit_window_attributes,
|
||||||
name.clone(),
|
name.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -172,22 +177,22 @@ impl WinitWindows {
|
||||||
height: constraints.max_height,
|
height: constraints.max_height,
|
||||||
};
|
};
|
||||||
|
|
||||||
let winit_window_builder =
|
let winit_window_attributes =
|
||||||
if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
|
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_min_inner_size(min_inner_size)
|
||||||
.with_max_inner_size(max_inner_size)
|
.with_max_inner_size(max_inner_size)
|
||||||
} else {
|
} else {
|
||||||
winit_window_builder.with_min_inner_size(min_inner_size)
|
winit_window_attributes.with_min_inner_size(min_inner_size)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use winit::platform::web::WindowBuilderExtWebSys;
|
use winit::platform::web::WindowAttributesExtWebSys;
|
||||||
|
|
||||||
if let Some(selector) = &window.canvas {
|
if let Some(selector) = &window.canvas {
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
|
@ -197,18 +202,18 @@ impl WinitWindows {
|
||||||
.expect("Cannot query for canvas element.");
|
.expect("Cannot query for canvas element.");
|
||||||
if let Some(canvas) = canvas {
|
if let Some(canvas) = canvas {
|
||||||
let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
|
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 {
|
} else {
|
||||||
panic!("Cannot find element: {}.", selector);
|
panic!("Cannot find element: {}.", selector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
winit_window_builder =
|
winit_window_attributes =
|
||||||
winit_window_builder.with_prevent_default(window.prevent_default_event_handling);
|
winit_window_attributes.with_prevent_default(window.prevent_default_event_handling);
|
||||||
winit_window_builder = winit_window_builder.with_append(true);
|
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();
|
let name = window.title.clone();
|
||||||
prepare_accessibility_for_window(
|
prepare_accessibility_for_window(
|
||||||
&winit_window,
|
&winit_window,
|
||||||
|
@ -247,7 +252,7 @@ impl WinitWindows {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the winit window that is associated with our entity.
|
/// 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
|
self.entity_to_winit
|
||||||
.get(&entity)
|
.get(&entity)
|
||||||
.and_then(|winit_id| self.windows.get(winit_id))
|
.and_then(|winit_id| self.windows.get(winit_id))
|
||||||
|
@ -256,17 +261,14 @@ impl WinitWindows {
|
||||||
/// Get the entity associated with the winit window id.
|
/// Get the entity associated with the winit window id.
|
||||||
///
|
///
|
||||||
/// This is mostly just an intermediary step between us and winit.
|
/// 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()
|
self.winit_to_entity.get(&winit_id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a window from winit.
|
/// Remove a window from winit.
|
||||||
///
|
///
|
||||||
/// This should mostly just be called when the window is closing.
|
/// This should mostly just be called when the window is closing.
|
||||||
pub fn remove_window(
|
pub fn remove_window(&mut self, entity: Entity) -> Option<WindowWrapper<WinitWindow>> {
|
||||||
&mut self,
|
|
||||||
entity: Entity,
|
|
||||||
) -> Option<WindowWrapper<winit::window::Window>> {
|
|
||||||
let winit_id = self.entity_to_winit.remove(&entity)?;
|
let winit_id = self.entity_to_winit.remove(&entity)?;
|
||||||
self.winit_to_entity.remove(&winit_id);
|
self.winit_to_entity.remove(&winit_id);
|
||||||
self.windows.remove(&winit_id)
|
self.windows.remove(&winit_id)
|
||||||
|
@ -276,11 +278,7 @@ impl WinitWindows {
|
||||||
/// Gets the "best" video mode which fits the given dimensions.
|
/// Gets the "best" video mode which fits the given dimensions.
|
||||||
///
|
///
|
||||||
/// The heuristic for "best" prioritizes width, height, and refresh rate in that order.
|
/// The heuristic for "best" prioritizes width, height, and refresh rate in that order.
|
||||||
pub fn get_fitting_videomode(
|
pub fn get_fitting_videomode(monitor: &MonitorHandle, width: u32, height: u32) -> VideoModeHandle {
|
||||||
monitor: &MonitorHandle,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> winit::monitor::VideoMode {
|
|
||||||
let mut modes = monitor.video_modes().collect::<Vec<_>>();
|
let mut modes = monitor.video_modes().collect::<Vec<_>>();
|
||||||
|
|
||||||
fn abs_diff(a: u32, b: u32) -> u32 {
|
fn abs_diff(a: u32, b: u32) -> u32 {
|
||||||
|
@ -308,10 +306,10 @@ pub fn get_fitting_videomode(
|
||||||
modes.first().unwrap().clone()
|
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.
|
/// 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<_>>();
|
let mut modes = monitor.video_modes().collect::<Vec<_>>();
|
||||||
modes.sort_by(|a, b| {
|
modes.sort_by(|a, b| {
|
||||||
use std::cmp::Ordering::*;
|
use std::cmp::Ordering::*;
|
||||||
|
@ -329,15 +327,15 @@ pub fn get_best_videomode(monitor: &MonitorHandle) -> winit::monitor::VideoMode
|
||||||
modes.first().unwrap().clone()
|
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 {
|
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
|
CursorGrabMode::Confined => winit_window
|
||||||
.set_cursor_grab(winit::window::CursorGrabMode::Confined)
|
.set_cursor_grab(WinitCursorGrabMode::Confined)
|
||||||
.or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Locked)),
|
.or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Locked)),
|
||||||
CursorGrabMode::Locked => winit_window
|
CursorGrabMode::Locked => winit_window
|
||||||
.set_cursor_grab(winit::window::CursorGrabMode::Locked)
|
.set_cursor_grab(WinitCursorGrabMode::Locked)
|
||||||
.or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Confined)),
|
.or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Confined)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = grab_result {
|
if let Err(err) = grab_result {
|
||||||
|
|
|
@ -459,6 +459,7 @@ Example | Description
|
||||||
Example | Description
|
Example | Description
|
||||||
--- | ---
|
--- | ---
|
||||||
[Clear Color](../examples/window/clear_color.rs) | Creates a solid color window
|
[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
|
[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
|
[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
|
[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::*,
|
color::palettes::basic::*,
|
||||||
input::touch::TouchPhase,
|
input::touch::TouchPhase,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::{ApplicationLifetime, WindowMode},
|
window::{AppLifecycle, WindowMode},
|
||||||
};
|
};
|
||||||
|
|
||||||
// the `bevy_main` proc_macro generates the required boilerplate for iOS and Android
|
// 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.
|
// Pause audio when app goes into background and resume when it returns.
|
||||||
// This is handled by the OS on iOS, but not on Android.
|
// This is handled by the OS on iOS, but not on Android.
|
||||||
fn handle_lifetime(
|
fn handle_lifetime(
|
||||||
mut lifetime_events: EventReader<ApplicationLifetime>,
|
mut lifecycle_events: EventReader<AppLifecycle>,
|
||||||
music_controller: Query<&AudioSink>,
|
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 {
|
match event {
|
||||||
ApplicationLifetime::Suspended => music_controller.single().pause(),
|
AppLifecycle::Idle | AppLifecycle::WillSuspend | AppLifecycle::WillResume => {}
|
||||||
ApplicationLifetime::Resumed => music_controller.single().play(),
|
AppLifecycle::Suspended => music_controller.pause(),
|
||||||
ApplicationLifetime::Started => (),
|
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
|
//! This is useful for making desktop applications, or any other program that doesn't need to be
|
||||||
//! running the event loop non-stop.
|
//! running the event loop non-stop.
|
||||||
|
|
||||||
|
use bevy::window::WindowResolution;
|
||||||
|
use bevy::winit::WakeUp;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
utils::Duration,
|
utils::Duration,
|
||||||
window::{PresentMode, RequestRedraw, WindowPlugin},
|
window::{PresentMode, WindowPlugin},
|
||||||
winit::{EventLoopProxy, WinitSettings},
|
winit::{EventLoopProxy, WinitSettings},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,15 +21,14 @@ fn main() {
|
||||||
// You can also customize update behavior with the fields of [`WinitSettings`]
|
// You can also customize update behavior with the fields of [`WinitSettings`]
|
||||||
.insert_resource(WinitSettings {
|
.insert_resource(WinitSettings {
|
||||||
focused_mode: bevy::winit::UpdateMode::Continuous,
|
focused_mode: bevy::winit::UpdateMode::Continuous,
|
||||||
unfocused_mode: bevy::winit::UpdateMode::ReactiveLowPower {
|
unfocused_mode: bevy::winit::UpdateMode::reactive_low_power(Duration::from_millis(10)),
|
||||||
wait: Duration::from_millis(10),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.insert_resource(ExampleMode::Game)
|
.insert_resource(ExampleMode::Game)
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
// Turn off vsync to maximize CPU/GPU usage
|
// Turn off vsync to maximize CPU/GPU usage
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
|
resolution: WindowResolution::new(800., 640.).with_scale_factor_override(1.),
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
|
@ -56,7 +57,7 @@ enum ExampleMode {
|
||||||
fn update_winit(
|
fn update_winit(
|
||||||
mode: Res<ExampleMode>,
|
mode: Res<ExampleMode>,
|
||||||
mut winit_config: ResMut<WinitSettings>,
|
mut winit_config: ResMut<WinitSettings>,
|
||||||
event_loop_proxy: NonSend<EventLoopProxy>,
|
event_loop_proxy: NonSend<EventLoopProxy<WakeUp>>,
|
||||||
) {
|
) {
|
||||||
use ExampleMode::*;
|
use ExampleMode::*;
|
||||||
*winit_config = match *mode {
|
*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
|
// (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
|
// [`RequestRedraw`] event is received, or one minute has passed without the app
|
||||||
// updating.
|
// 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 => {
|
ApplicationWithRedraw => {
|
||||||
// Sending a `RequestRedraw` event is useful when you want the app to update the next
|
// 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.
|
// 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
|
// Note that in this example the RequestRedraw winit event will make the app run in the same
|
||||||
// way as continuous
|
// way as continuous
|
||||||
let _ = event_loop_proxy.send_event(RequestRedraw);
|
let _ = event_loop_proxy.send_event(WakeUp);
|
||||||
WinitSettings::desktop_app()
|
WinitSettings::desktop_app()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
use bevy::{prelude::*, window::WindowResolution};
|
use bevy::{prelude::*, window::WindowResolution};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct CustomText;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
|
@ -39,7 +42,7 @@ fn setup(mut commands: Commands) {
|
||||||
parent
|
parent
|
||||||
.spawn(NodeBundle {
|
.spawn(NodeBundle {
|
||||||
style: Style {
|
style: Style {
|
||||||
width: Val::Px(200.0),
|
width: Val::Px(300.0),
|
||||||
height: Val::Percent(100.0),
|
height: Val::Percent(100.0),
|
||||||
border: UiRect::all(Val::Px(2.0)),
|
border: UiRect::all(Val::Px(2.0)),
|
||||||
..default()
|
..default()
|
||||||
|
@ -48,7 +51,8 @@ fn setup(mut commands: Commands) {
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(
|
parent.spawn((
|
||||||
|
CustomText,
|
||||||
TextBundle::from_section(
|
TextBundle::from_section(
|
||||||
"Example text",
|
"Example text",
|
||||||
TextStyle {
|
TextStyle {
|
||||||
|
@ -60,19 +64,32 @@ fn setup(mut commands: Commands) {
|
||||||
align_self: AlignSelf::FlexEnd,
|
align_self: AlignSelf::FlexEnd,
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
);
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the title of the window to the current override
|
/// 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();
|
let mut window = windows.single_mut();
|
||||||
|
|
||||||
window.title = format!(
|
let text = format!(
|
||||||
"Scale override: {:?}",
|
"Scale factor: {:.1} {}",
|
||||||
window.resolution.scale_factor_override()
|
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
|
/// This system toggles scale factor overrides when enter is pressed
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs
|
diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs
|
||||||
index 63d79b1d2..83ed56293 100644
|
index df0aab42d..6e28a6e9c 100644
|
||||||
--- a/crates/bevy_winit/src/lib.rs
|
--- a/crates/bevy_winit/src/state.rs
|
||||||
+++ b/crates/bevy_winit/src/lib.rs
|
+++ b/crates/bevy_winit/src/state.rs
|
||||||
@@ -429,6 +429,12 @@ fn handle_winit_event(
|
@@ -208,6 +208,12 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
|
||||||
|
}
|
||||||
runner_state.window_event_received = true;
|
}
|
||||||
|
|
||||||
+ window_resized.send(WindowResized {
|
+ window_resized.send(WindowResized {
|
||||||
+ window,
|
+ window,
|
||||||
|
@ -14,4 +14,4 @@ index 63d79b1d2..83ed56293 100644
|
||||||
+
|
+
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::Resized(size) => {
|
WindowEvent::Resized(size) => {
|
||||||
react_to_resize(&mut win, size, &mut window_resized, window);
|
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
|
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
|
--- a/crates/bevy_winit/src/winit_config.rs
|
||||||
+++ b/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.
|
/// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy.
|
||||||
pub fn desktop_app() -> Self {
|
pub fn desktop_app() -> Self {
|
||||||
- WinitSettings {
|
- WinitSettings {
|
||||||
- focused_mode: UpdateMode::Reactive {
|
- focused_mode: UpdateMode::reactive(Duration::from_secs(5)),
|
||||||
- wait: Duration::from_secs(5),
|
- unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(60)),
|
||||||
- },
|
|
||||||
- unfocused_mode: UpdateMode::ReactiveLowPower {
|
|
||||||
- wait: Duration::from_secs(60),
|
|
||||||
- },
|
|
||||||
- }
|
- }
|
||||||
+ Self::default()
|
+ Self::default()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue