Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"670a056c974c06b39505dd956833d3104b47528354f2980b6d19f9514b3dd2d0","Cargo.toml":"99dccd99c04b949fdfef3efd197bb315b8a1d54e8a79f3ce33bc96a36f9ec56a","LICENSE-APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE-MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"209e660f0261b48f66c92a254dad875a46fddd776d56fbc9e40e35566d260cc8","src/accessibility.rs":"01a493b4e8b95b42fbf65841411867e9eaaf6853ad870e7104a48ac37c41d88a","src/converters.rs":"08a03a8cd5e1a0589bf3516b86da3f9fa7e13032fc66e500e403a298ac303def","src/cursor.rs":"df5bad2a52dc1c5a2d282fc3553b2461c7a87ed1e4a0d5c55d86dde00583c605","src/custom_cursor.rs":"9ae027c22cda57f769a8937dcfbbebb64eb64a41004ae4dccc92d66f55d71f4e","src/lib.rs":"33382cebe09d73e1c8cc86ae1dcd5f425f7756b1a5513f6428ccadc350362c85","src/state.rs":"739cb855715bfffbcde4a54bc0e69df56837c1dab2c266d9bac4328f11f05122","src/system.rs":"a91f9616d24380e098e4a331c5589525cce24a382ac9bc98e496f2f6d840420f","src/winit_config.rs":"c6117b5e2f1b10242203ffd8cde98cd64ed899df5f6ab7b4be7b0ae0d51b16e0","src/winit_monitors.rs":"36c6e6093d8be7470c201dfa6a9575494e0c20446fad75d4808c3de2d965d50c","src/winit_windows.rs":"e86d9db29b6645c79e74ec770dc9d94ee723fba0f2e47b7fa5720704e88ed7f3"},"package":"6a5e7f00c6b3b6823df5ec2a5e9067273607208919bc8c211773ebb9643c87f0"}

3809
vendor/bevy_winit/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

221
vendor/bevy_winit/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,221 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_winit"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A winit window and input backend for Bevy Engine"
homepage = "https://bevyengine.org"
readme = "README.md"
keywords = ["bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"-Zunstable-options",
"--generate-link-to-definition",
]
[features]
accesskit_unix = [
"accesskit_winit/accesskit_unix",
"accesskit_winit/async-io",
]
android-game-activity = ["winit/android-game-activity"]
android-native-activity = ["winit/android-native-activity"]
custom_cursor = [
"bevy_image",
"bevy_asset",
"bytemuck",
"wgpu-types",
]
default = ["x11"]
serialize = [
"serde",
"bevy_input/serialize",
"bevy_window/serialize",
"bevy_platform/serialize",
]
trace = []
wayland = [
"winit/wayland",
"winit/wayland-csd-adwaita",
]
x11 = ["winit/x11"]
[lib]
name = "bevy_winit"
path = "src/lib.rs"
[dependencies.accesskit]
version = "0.18"
[dependencies.accesskit_winit]
version = "0.25"
features = ["rwh_06"]
default-features = false
[dependencies.approx]
version = "0.5"
default-features = false
[dependencies.bevy_a11y]
version = "0.16.1"
[dependencies.bevy_app]
version = "0.16.1"
[dependencies.bevy_asset]
version = "0.16.1"
optional = true
[dependencies.bevy_derive]
version = "0.16.1"
[dependencies.bevy_ecs]
version = "0.16.1"
[dependencies.bevy_image]
version = "0.16.1"
optional = true
[dependencies.bevy_input]
version = "0.16.1"
[dependencies.bevy_input_focus]
version = "0.16.1"
[dependencies.bevy_log]
version = "0.16.1"
[dependencies.bevy_math]
version = "0.16.1"
[dependencies.bevy_platform]
version = "0.16.1"
features = ["std"]
default-features = false
[dependencies.bevy_reflect]
version = "0.16.1"
[dependencies.bevy_tasks]
version = "0.16.1"
[dependencies.bevy_utils]
version = "0.16.1"
[dependencies.bevy_window]
version = "0.16.1"
[dependencies.bytemuck]
version = "1.5"
optional = true
[dependencies.cfg-if]
version = "1.0"
[dependencies.raw-window-handle]
version = "0.6"
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dependencies.tracing]
version = "0.1"
features = ["std"]
default-features = false
[dependencies.wgpu-types]
version = "24"
optional = true
[dependencies.winit]
version = "0.30"
features = ["rwh_06"]
default-features = false
[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_app]
version = "0.16.1"
features = ["web"]
default-features = false
[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_platform]
version = "0.16.1"
features = ["web"]
default-features = false
[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_reflect]
version = "0.16.1"
features = ["web"]
default-features = false
[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_tasks]
version = "0.16.1"
features = ["web"]
default-features = false
[target.'cfg(target_arch = "wasm32")'.dependencies.crossbeam-channel]
version = "0.5"
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2"
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3"
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_winit/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_winit/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
vendor/bevy_winit/README.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# Bevy Winit
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy_winit.svg)](https://crates.io/crates/bevy_winit)
[![Downloads](https://img.shields.io/crates/d/bevy_winit.svg)](https://crates.io/crates/bevy_winit)
[![Docs](https://docs.rs/bevy_winit/badge.svg)](https://docs.rs/bevy_winit/latest/bevy_winit/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)

308
vendor/bevy_winit/src/accessibility.rs vendored Normal file
View File

@@ -0,0 +1,308 @@
//! Helpers for mapping window entities to accessibility types
use alloc::{collections::VecDeque, sync::Arc};
use bevy_input_focus::InputFocus;
use std::sync::Mutex;
use winit::event_loop::ActiveEventLoop;
use accesskit::{
ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree,
TreeUpdate,
};
use accesskit_winit::Adapter;
use bevy_a11y::{
AccessibilityNode, AccessibilityRequested, AccessibilitySystem,
ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates,
};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{entity::EntityHashMap, prelude::*};
use bevy_window::{PrimaryWindow, Window, WindowClosed};
/// Maps window entities to their `AccessKit` [`Adapter`]s.
#[derive(Default, Deref, DerefMut)]
pub struct AccessKitAdapters(pub EntityHashMap<Adapter>);
/// Maps window entities to their respective [`ActionRequest`]s.
#[derive(Resource, Default, Deref, DerefMut)]
pub struct WinitActionRequestHandlers(pub EntityHashMap<Arc<Mutex<WinitActionRequestHandler>>>);
/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel.
#[derive(Clone, Default, Deref, DerefMut)]
pub struct 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 node = Node::new(Role::Window);
node.set_label(self.name.clone());
node
}
fn build_initial_tree(&mut self) -> TreeUpdate {
let root = self.build_root();
let accesskit_window_id = NodeId(self.entity.to_bits());
let tree = Tree::new(accesskit_window_id);
self.requested.set(true);
TreeUpdate {
nodes: vec![(accesskit_window_id, root)],
tree: Some(tree),
focus: accesskit_window_id,
}
}
}
struct WinitActivationHandler(Arc<Mutex<AccessKitState>>);
impl ActivationHandler for WinitActivationHandler {
fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
Some(self.0.lock().unwrap().build_initial_tree())
}
}
impl WinitActivationHandler {
pub fn new(state: Arc<Mutex<AccessKitState>>) -> Self {
Self(state)
}
}
#[derive(Clone, Default)]
struct WinitActionHandler(Arc<Mutex<WinitActionRequestHandler>>);
impl ActionHandler for WinitActionHandler {
fn do_action(&mut self, request: ActionRequest) {
let mut requests = self.0.lock().unwrap();
requests.push_back(request);
}
}
impl WinitActionHandler {
pub fn new(handler: Arc<Mutex<WinitActionRequestHandler>>) -> Self {
Self(handler)
}
}
struct WinitDeactivationHandler;
impl DeactivationHandler for WinitDeactivationHandler {
fn deactivate_accessibility(&mut self) {}
}
/// Prepares accessibility for a winit window.
pub(crate) fn prepare_accessibility_for_window(
event_loop: &ActiveEventLoop,
winit_window: &winit::window::Window,
entity: Entity,
name: String,
accessibility_requested: AccessibilityRequested,
adapters: &mut AccessKitAdapters,
handlers: &mut WinitActionRequestHandlers,
) {
let state = AccessKitState::new(name, entity, accessibility_requested);
let activation_handler = WinitActivationHandler::new(Arc::clone(&state));
let action_request_handler = WinitActionRequestHandler::new();
let action_handler = WinitActionHandler::new(Arc::clone(&action_request_handler));
let deactivation_handler = WinitDeactivationHandler;
let adapter = Adapter::with_direct_handlers(
event_loop,
winit_window,
activation_handler,
action_handler,
deactivation_handler,
);
adapters.insert(entity, adapter);
handlers.insert(entity, action_request_handler);
}
fn window_closed(
mut adapters: NonSendMut<AccessKitAdapters>,
mut handlers: ResMut<WinitActionRequestHandlers>,
mut events: EventReader<WindowClosed>,
) {
for WindowClosed { window, .. } in events.read() {
adapters.remove(window);
handlers.remove(window);
}
}
fn poll_receivers(
handlers: Res<WinitActionRequestHandlers>,
mut actions: EventWriter<ActionRequestWrapper>,
) {
for (_id, handler) in handlers.iter() {
let mut handler = handler.lock().unwrap();
while let Some(event) = handler.pop_front() {
actions.write(ActionRequestWrapper(event));
}
}
}
fn should_update_accessibility_nodes(
accessibility_requested: Res<AccessibilityRequested>,
manage_accessibility_updates: Res<ManageAccessibilityUpdates>,
) -> bool {
accessibility_requested.get() && manage_accessibility_updates.get()
}
fn update_accessibility_nodes(
mut adapters: NonSendMut<AccessKitAdapters>,
focus: Option<Res<InputFocus>>,
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
nodes: Query<(
Entity,
&AccessibilityNode,
Option<&Children>,
Option<&ChildOf>,
)>,
node_entities: Query<Entity, With<AccessibilityNode>>,
) {
let Ok((primary_window_id, primary_window)) = primary_window.single() else {
return;
};
let Some(adapter) = adapters.get_mut(&primary_window_id) else {
return;
};
let Some(focus) = focus else {
return;
};
if focus.is_changed() || !nodes.is_empty() {
// Don't panic if the focused entity does not currently exist
// It's probably waiting to be spawned
if let Some(focused_entity) = focus.0 {
if !node_entities.contains(focused_entity) {
return;
}
}
adapter.update_if_active(|| {
update_adapter(
nodes,
node_entities,
primary_window,
primary_window_id,
focus,
)
});
}
}
fn update_adapter(
nodes: Query<(
Entity,
&AccessibilityNode,
Option<&Children>,
Option<&ChildOf>,
)>,
node_entities: Query<Entity, With<AccessibilityNode>>,
primary_window: &Window,
primary_window_id: Entity,
focus: Res<InputFocus>,
) -> TreeUpdate {
let mut to_update = vec![];
let mut window_children = vec![];
for (entity, node, children, child_of) in &nodes {
let mut node = (**node).clone();
queue_node_for_update(entity, child_of, &node_entities, &mut window_children);
add_children_nodes(children, &node_entities, &mut node);
let node_id = NodeId(entity.to_bits());
to_update.push((node_id, node));
}
let mut window_node = Node::new(Role::Window);
if primary_window.focused {
let title = primary_window.title.clone();
window_node.set_label(title.into_boxed_str());
}
window_node.set_children(window_children);
let node_id = NodeId(primary_window_id.to_bits());
let window_update = (node_id, window_node);
to_update.insert(0, window_update);
TreeUpdate {
nodes: to_update,
tree: None,
focus: NodeId(focus.0.unwrap_or(primary_window_id).to_bits()),
}
}
#[inline]
fn queue_node_for_update(
node_entity: Entity,
child_of: Option<&ChildOf>,
node_entities: &Query<Entity, With<AccessibilityNode>>,
window_children: &mut Vec<NodeId>,
) {
let should_push = if let Some(child_of) = child_of {
!node_entities.contains(child_of.parent())
} else {
true
};
if should_push {
window_children.push(NodeId(node_entity.to_bits()));
}
}
#[inline]
fn add_children_nodes(
children: Option<&Children>,
node_entities: &Query<Entity, With<AccessibilityNode>>,
node: &mut Node,
) {
let Some(children) = children else {
return;
};
for child in children {
if node_entities.contains(*child) {
node.push_child(NodeId(child.to_bits()));
}
}
}
/// Implements winit-specific `AccessKit` functionality.
pub struct AccessKitPlugin;
impl Plugin for AccessKitPlugin {
fn build(&self, app: &mut App) {
app.init_non_send_resource::<AccessKitAdapters>()
.init_resource::<WinitActionRequestHandlers>()
.add_event::<ActionRequestWrapper>()
.add_systems(
PostUpdate,
(
poll_receivers,
update_accessibility_nodes.run_if(should_update_accessibility_nodes),
window_closed
.before(poll_receivers)
.before(update_accessibility_nodes),
)
.in_set(AccessibilitySystem::Update),
);
}
}

720
vendor/bevy_winit/src/converters.rs vendored Normal file
View File

@@ -0,0 +1,720 @@
use bevy_ecs::entity::Entity;
use bevy_input::{
keyboard::{KeyCode, KeyboardInput, NativeKeyCode},
mouse::MouseButton,
touch::{ForceTouch, TouchInput, TouchPhase},
ButtonState,
};
use bevy_math::{CompassOctant, Vec2};
use bevy_window::SystemCursorIcon;
use bevy_window::{EnabledButtons, WindowLevel, WindowTheme};
use winit::keyboard::{Key, NamedKey, NativeKey};
pub fn convert_keyboard_input(
keyboard_input: &winit::event::KeyEvent,
window: Entity,
) -> KeyboardInput {
KeyboardInput {
state: convert_element_state(keyboard_input.state),
key_code: convert_physical_key_code(keyboard_input.physical_key),
logical_key: convert_logical_key(&keyboard_input.logical_key),
text: keyboard_input.text.clone(),
repeat: keyboard_input.repeat,
window,
}
}
pub fn convert_element_state(element_state: winit::event::ElementState) -> ButtonState {
match element_state {
winit::event::ElementState::Pressed => ButtonState::Pressed,
winit::event::ElementState::Released => ButtonState::Released,
}
}
pub fn convert_mouse_button(mouse_button: winit::event::MouseButton) -> MouseButton {
match mouse_button {
winit::event::MouseButton::Left => MouseButton::Left,
winit::event::MouseButton::Right => MouseButton::Right,
winit::event::MouseButton::Middle => MouseButton::Middle,
winit::event::MouseButton::Back => MouseButton::Back,
winit::event::MouseButton::Forward => MouseButton::Forward,
winit::event::MouseButton::Other(val) => MouseButton::Other(val),
}
}
pub fn convert_touch_input(
touch_input: winit::event::Touch,
location: winit::dpi::LogicalPosition<f64>,
window_entity: Entity,
) -> TouchInput {
TouchInput {
phase: match touch_input.phase {
winit::event::TouchPhase::Started => TouchPhase::Started,
winit::event::TouchPhase::Moved => TouchPhase::Moved,
winit::event::TouchPhase::Ended => TouchPhase::Ended,
winit::event::TouchPhase::Cancelled => TouchPhase::Canceled,
},
position: Vec2::new(location.x as f32, location.y as f32),
window: window_entity,
force: touch_input.force.map(|f| match f {
winit::event::Force::Calibrated {
force,
max_possible_force,
altitude_angle,
} => ForceTouch::Calibrated {
force,
max_possible_force,
altitude_angle,
},
winit::event::Force::Normalized(x) => ForceTouch::Normalized(x),
}),
id: touch_input.id,
}
}
pub fn convert_physical_native_key_code(
native_key_code: winit::keyboard::NativeKeyCode,
) -> NativeKeyCode {
match native_key_code {
winit::keyboard::NativeKeyCode::Unidentified => NativeKeyCode::Unidentified,
winit::keyboard::NativeKeyCode::Android(scan_code) => NativeKeyCode::Android(scan_code),
winit::keyboard::NativeKeyCode::MacOS(scan_code) => NativeKeyCode::MacOS(scan_code),
winit::keyboard::NativeKeyCode::Windows(scan_code) => NativeKeyCode::Windows(scan_code),
winit::keyboard::NativeKeyCode::Xkb(key_code) => NativeKeyCode::Xkb(key_code),
}
}
pub fn convert_physical_key_code(virtual_key_code: winit::keyboard::PhysicalKey) -> KeyCode {
match virtual_key_code {
winit::keyboard::PhysicalKey::Unidentified(native_key_code) => {
KeyCode::Unidentified(convert_physical_native_key_code(native_key_code))
}
winit::keyboard::PhysicalKey::Code(code) => match code {
winit::keyboard::KeyCode::Backquote => KeyCode::Backquote,
winit::keyboard::KeyCode::Backslash => KeyCode::Backslash,
winit::keyboard::KeyCode::BracketLeft => KeyCode::BracketLeft,
winit::keyboard::KeyCode::BracketRight => KeyCode::BracketRight,
winit::keyboard::KeyCode::Comma => KeyCode::Comma,
winit::keyboard::KeyCode::Digit0 => KeyCode::Digit0,
winit::keyboard::KeyCode::Digit1 => KeyCode::Digit1,
winit::keyboard::KeyCode::Digit2 => KeyCode::Digit2,
winit::keyboard::KeyCode::Digit3 => KeyCode::Digit3,
winit::keyboard::KeyCode::Digit4 => KeyCode::Digit4,
winit::keyboard::KeyCode::Digit5 => KeyCode::Digit5,
winit::keyboard::KeyCode::Digit6 => KeyCode::Digit6,
winit::keyboard::KeyCode::Digit7 => KeyCode::Digit7,
winit::keyboard::KeyCode::Digit8 => KeyCode::Digit8,
winit::keyboard::KeyCode::Digit9 => KeyCode::Digit9,
winit::keyboard::KeyCode::Equal => KeyCode::Equal,
winit::keyboard::KeyCode::IntlBackslash => KeyCode::IntlBackslash,
winit::keyboard::KeyCode::IntlRo => KeyCode::IntlRo,
winit::keyboard::KeyCode::IntlYen => KeyCode::IntlYen,
winit::keyboard::KeyCode::KeyA => KeyCode::KeyA,
winit::keyboard::KeyCode::KeyB => KeyCode::KeyB,
winit::keyboard::KeyCode::KeyC => KeyCode::KeyC,
winit::keyboard::KeyCode::KeyD => KeyCode::KeyD,
winit::keyboard::KeyCode::KeyE => KeyCode::KeyE,
winit::keyboard::KeyCode::KeyF => KeyCode::KeyF,
winit::keyboard::KeyCode::KeyG => KeyCode::KeyG,
winit::keyboard::KeyCode::KeyH => KeyCode::KeyH,
winit::keyboard::KeyCode::KeyI => KeyCode::KeyI,
winit::keyboard::KeyCode::KeyJ => KeyCode::KeyJ,
winit::keyboard::KeyCode::KeyK => KeyCode::KeyK,
winit::keyboard::KeyCode::KeyL => KeyCode::KeyL,
winit::keyboard::KeyCode::KeyM => KeyCode::KeyM,
winit::keyboard::KeyCode::KeyN => KeyCode::KeyN,
winit::keyboard::KeyCode::KeyO => KeyCode::KeyO,
winit::keyboard::KeyCode::KeyP => KeyCode::KeyP,
winit::keyboard::KeyCode::KeyQ => KeyCode::KeyQ,
winit::keyboard::KeyCode::KeyR => KeyCode::KeyR,
winit::keyboard::KeyCode::KeyS => KeyCode::KeyS,
winit::keyboard::KeyCode::KeyT => KeyCode::KeyT,
winit::keyboard::KeyCode::KeyU => KeyCode::KeyU,
winit::keyboard::KeyCode::KeyV => KeyCode::KeyV,
winit::keyboard::KeyCode::KeyW => KeyCode::KeyW,
winit::keyboard::KeyCode::KeyX => KeyCode::KeyX,
winit::keyboard::KeyCode::KeyY => KeyCode::KeyY,
winit::keyboard::KeyCode::KeyZ => KeyCode::KeyZ,
winit::keyboard::KeyCode::Minus => KeyCode::Minus,
winit::keyboard::KeyCode::Period => KeyCode::Period,
winit::keyboard::KeyCode::Quote => KeyCode::Quote,
winit::keyboard::KeyCode::Semicolon => KeyCode::Semicolon,
winit::keyboard::KeyCode::Slash => KeyCode::Slash,
winit::keyboard::KeyCode::AltLeft => KeyCode::AltLeft,
winit::keyboard::KeyCode::AltRight => KeyCode::AltRight,
winit::keyboard::KeyCode::Backspace => KeyCode::Backspace,
winit::keyboard::KeyCode::CapsLock => KeyCode::CapsLock,
winit::keyboard::KeyCode::ContextMenu => KeyCode::ContextMenu,
winit::keyboard::KeyCode::ControlLeft => KeyCode::ControlLeft,
winit::keyboard::KeyCode::ControlRight => KeyCode::ControlRight,
winit::keyboard::KeyCode::Enter => KeyCode::Enter,
winit::keyboard::KeyCode::SuperLeft => KeyCode::SuperLeft,
winit::keyboard::KeyCode::SuperRight => KeyCode::SuperRight,
winit::keyboard::KeyCode::ShiftLeft => KeyCode::ShiftLeft,
winit::keyboard::KeyCode::ShiftRight => KeyCode::ShiftRight,
winit::keyboard::KeyCode::Space => KeyCode::Space,
winit::keyboard::KeyCode::Tab => KeyCode::Tab,
winit::keyboard::KeyCode::Convert => KeyCode::Convert,
winit::keyboard::KeyCode::KanaMode => KeyCode::KanaMode,
winit::keyboard::KeyCode::Lang1 => KeyCode::Lang1,
winit::keyboard::KeyCode::Lang2 => KeyCode::Lang2,
winit::keyboard::KeyCode::Lang3 => KeyCode::Lang3,
winit::keyboard::KeyCode::Lang4 => KeyCode::Lang4,
winit::keyboard::KeyCode::Lang5 => KeyCode::Lang5,
winit::keyboard::KeyCode::NonConvert => KeyCode::NonConvert,
winit::keyboard::KeyCode::Delete => KeyCode::Delete,
winit::keyboard::KeyCode::End => KeyCode::End,
winit::keyboard::KeyCode::Help => KeyCode::Help,
winit::keyboard::KeyCode::Home => KeyCode::Home,
winit::keyboard::KeyCode::Insert => KeyCode::Insert,
winit::keyboard::KeyCode::PageDown => KeyCode::PageDown,
winit::keyboard::KeyCode::PageUp => KeyCode::PageUp,
winit::keyboard::KeyCode::ArrowDown => KeyCode::ArrowDown,
winit::keyboard::KeyCode::ArrowLeft => KeyCode::ArrowLeft,
winit::keyboard::KeyCode::ArrowRight => KeyCode::ArrowRight,
winit::keyboard::KeyCode::ArrowUp => KeyCode::ArrowUp,
winit::keyboard::KeyCode::NumLock => KeyCode::NumLock,
winit::keyboard::KeyCode::Numpad0 => KeyCode::Numpad0,
winit::keyboard::KeyCode::Numpad1 => KeyCode::Numpad1,
winit::keyboard::KeyCode::Numpad2 => KeyCode::Numpad2,
winit::keyboard::KeyCode::Numpad3 => KeyCode::Numpad3,
winit::keyboard::KeyCode::Numpad4 => KeyCode::Numpad4,
winit::keyboard::KeyCode::Numpad5 => KeyCode::Numpad5,
winit::keyboard::KeyCode::Numpad6 => KeyCode::Numpad6,
winit::keyboard::KeyCode::Numpad7 => KeyCode::Numpad7,
winit::keyboard::KeyCode::Numpad8 => KeyCode::Numpad8,
winit::keyboard::KeyCode::Numpad9 => KeyCode::Numpad9,
winit::keyboard::KeyCode::NumpadAdd => KeyCode::NumpadAdd,
winit::keyboard::KeyCode::NumpadBackspace => KeyCode::NumpadBackspace,
winit::keyboard::KeyCode::NumpadClear => KeyCode::NumpadClear,
winit::keyboard::KeyCode::NumpadClearEntry => KeyCode::NumpadClearEntry,
winit::keyboard::KeyCode::NumpadComma => KeyCode::NumpadComma,
winit::keyboard::KeyCode::NumpadDecimal => KeyCode::NumpadDecimal,
winit::keyboard::KeyCode::NumpadDivide => KeyCode::NumpadDivide,
winit::keyboard::KeyCode::NumpadEnter => KeyCode::NumpadEnter,
winit::keyboard::KeyCode::NumpadEqual => KeyCode::NumpadEqual,
winit::keyboard::KeyCode::NumpadHash => KeyCode::NumpadHash,
winit::keyboard::KeyCode::NumpadMemoryAdd => KeyCode::NumpadMemoryAdd,
winit::keyboard::KeyCode::NumpadMemoryClear => KeyCode::NumpadMemoryClear,
winit::keyboard::KeyCode::NumpadMemoryRecall => KeyCode::NumpadMemoryRecall,
winit::keyboard::KeyCode::NumpadMemoryStore => KeyCode::NumpadMemoryStore,
winit::keyboard::KeyCode::NumpadMemorySubtract => KeyCode::NumpadMemorySubtract,
winit::keyboard::KeyCode::NumpadMultiply => KeyCode::NumpadMultiply,
winit::keyboard::KeyCode::NumpadParenLeft => KeyCode::NumpadParenLeft,
winit::keyboard::KeyCode::NumpadParenRight => KeyCode::NumpadParenRight,
winit::keyboard::KeyCode::NumpadStar => KeyCode::NumpadStar,
winit::keyboard::KeyCode::NumpadSubtract => KeyCode::NumpadSubtract,
winit::keyboard::KeyCode::Escape => KeyCode::Escape,
winit::keyboard::KeyCode::Fn => KeyCode::Fn,
winit::keyboard::KeyCode::FnLock => KeyCode::FnLock,
winit::keyboard::KeyCode::PrintScreen => KeyCode::PrintScreen,
winit::keyboard::KeyCode::ScrollLock => KeyCode::ScrollLock,
winit::keyboard::KeyCode::Pause => KeyCode::Pause,
winit::keyboard::KeyCode::BrowserBack => KeyCode::BrowserBack,
winit::keyboard::KeyCode::BrowserFavorites => KeyCode::BrowserFavorites,
winit::keyboard::KeyCode::BrowserForward => KeyCode::BrowserForward,
winit::keyboard::KeyCode::BrowserHome => KeyCode::BrowserHome,
winit::keyboard::KeyCode::BrowserRefresh => KeyCode::BrowserRefresh,
winit::keyboard::KeyCode::BrowserSearch => KeyCode::BrowserSearch,
winit::keyboard::KeyCode::BrowserStop => KeyCode::BrowserStop,
winit::keyboard::KeyCode::Eject => KeyCode::Eject,
winit::keyboard::KeyCode::LaunchApp1 => KeyCode::LaunchApp1,
winit::keyboard::KeyCode::LaunchApp2 => KeyCode::LaunchApp2,
winit::keyboard::KeyCode::LaunchMail => KeyCode::LaunchMail,
winit::keyboard::KeyCode::MediaPlayPause => KeyCode::MediaPlayPause,
winit::keyboard::KeyCode::MediaSelect => KeyCode::MediaSelect,
winit::keyboard::KeyCode::MediaStop => KeyCode::MediaStop,
winit::keyboard::KeyCode::MediaTrackNext => KeyCode::MediaTrackNext,
winit::keyboard::KeyCode::MediaTrackPrevious => KeyCode::MediaTrackPrevious,
winit::keyboard::KeyCode::Power => KeyCode::Power,
winit::keyboard::KeyCode::Sleep => KeyCode::Sleep,
winit::keyboard::KeyCode::AudioVolumeDown => KeyCode::AudioVolumeDown,
winit::keyboard::KeyCode::AudioVolumeMute => KeyCode::AudioVolumeMute,
winit::keyboard::KeyCode::AudioVolumeUp => KeyCode::AudioVolumeUp,
winit::keyboard::KeyCode::WakeUp => KeyCode::WakeUp,
winit::keyboard::KeyCode::Meta => KeyCode::Meta,
winit::keyboard::KeyCode::Hyper => KeyCode::Hyper,
winit::keyboard::KeyCode::Turbo => KeyCode::Turbo,
winit::keyboard::KeyCode::Abort => KeyCode::Abort,
winit::keyboard::KeyCode::Resume => KeyCode::Resume,
winit::keyboard::KeyCode::Suspend => KeyCode::Suspend,
winit::keyboard::KeyCode::Again => KeyCode::Again,
winit::keyboard::KeyCode::Copy => KeyCode::Copy,
winit::keyboard::KeyCode::Cut => KeyCode::Cut,
winit::keyboard::KeyCode::Find => KeyCode::Find,
winit::keyboard::KeyCode::Open => KeyCode::Open,
winit::keyboard::KeyCode::Paste => KeyCode::Paste,
winit::keyboard::KeyCode::Props => KeyCode::Props,
winit::keyboard::KeyCode::Select => KeyCode::Select,
winit::keyboard::KeyCode::Undo => KeyCode::Undo,
winit::keyboard::KeyCode::Hiragana => KeyCode::Hiragana,
winit::keyboard::KeyCode::Katakana => KeyCode::Katakana,
winit::keyboard::KeyCode::F1 => KeyCode::F1,
winit::keyboard::KeyCode::F2 => KeyCode::F2,
winit::keyboard::KeyCode::F3 => KeyCode::F3,
winit::keyboard::KeyCode::F4 => KeyCode::F4,
winit::keyboard::KeyCode::F5 => KeyCode::F5,
winit::keyboard::KeyCode::F6 => KeyCode::F6,
winit::keyboard::KeyCode::F7 => KeyCode::F7,
winit::keyboard::KeyCode::F8 => KeyCode::F8,
winit::keyboard::KeyCode::F9 => KeyCode::F9,
winit::keyboard::KeyCode::F10 => KeyCode::F10,
winit::keyboard::KeyCode::F11 => KeyCode::F11,
winit::keyboard::KeyCode::F12 => KeyCode::F12,
winit::keyboard::KeyCode::F13 => KeyCode::F13,
winit::keyboard::KeyCode::F14 => KeyCode::F14,
winit::keyboard::KeyCode::F15 => KeyCode::F15,
winit::keyboard::KeyCode::F16 => KeyCode::F16,
winit::keyboard::KeyCode::F17 => KeyCode::F17,
winit::keyboard::KeyCode::F18 => KeyCode::F18,
winit::keyboard::KeyCode::F19 => KeyCode::F19,
winit::keyboard::KeyCode::F20 => KeyCode::F20,
winit::keyboard::KeyCode::F21 => KeyCode::F21,
winit::keyboard::KeyCode::F22 => KeyCode::F22,
winit::keyboard::KeyCode::F23 => KeyCode::F23,
winit::keyboard::KeyCode::F24 => KeyCode::F24,
winit::keyboard::KeyCode::F25 => KeyCode::F25,
winit::keyboard::KeyCode::F26 => KeyCode::F26,
winit::keyboard::KeyCode::F27 => KeyCode::F27,
winit::keyboard::KeyCode::F28 => KeyCode::F28,
winit::keyboard::KeyCode::F29 => KeyCode::F29,
winit::keyboard::KeyCode::F30 => KeyCode::F30,
winit::keyboard::KeyCode::F31 => KeyCode::F31,
winit::keyboard::KeyCode::F32 => KeyCode::F32,
winit::keyboard::KeyCode::F33 => KeyCode::F33,
winit::keyboard::KeyCode::F34 => KeyCode::F34,
winit::keyboard::KeyCode::F35 => KeyCode::F35,
_ => KeyCode::Unidentified(NativeKeyCode::Unidentified),
},
}
}
pub fn convert_logical_key(logical_key_code: &Key) -> bevy_input::keyboard::Key {
match logical_key_code {
Key::Character(s) => bevy_input::keyboard::Key::Character(s.clone()),
Key::Unidentified(nk) => bevy_input::keyboard::Key::Unidentified(convert_native_key(nk)),
Key::Dead(c) => bevy_input::keyboard::Key::Dead(c.to_owned()),
Key::Named(NamedKey::Alt) => bevy_input::keyboard::Key::Alt,
Key::Named(NamedKey::AltGraph) => bevy_input::keyboard::Key::AltGraph,
Key::Named(NamedKey::CapsLock) => bevy_input::keyboard::Key::CapsLock,
Key::Named(NamedKey::Control) => bevy_input::keyboard::Key::Control,
Key::Named(NamedKey::Fn) => bevy_input::keyboard::Key::Fn,
Key::Named(NamedKey::FnLock) => bevy_input::keyboard::Key::FnLock,
Key::Named(NamedKey::NumLock) => bevy_input::keyboard::Key::NumLock,
Key::Named(NamedKey::ScrollLock) => bevy_input::keyboard::Key::ScrollLock,
Key::Named(NamedKey::Shift) => bevy_input::keyboard::Key::Shift,
Key::Named(NamedKey::Symbol) => bevy_input::keyboard::Key::Symbol,
Key::Named(NamedKey::SymbolLock) => bevy_input::keyboard::Key::SymbolLock,
Key::Named(NamedKey::Meta) => bevy_input::keyboard::Key::Meta,
Key::Named(NamedKey::Hyper) => bevy_input::keyboard::Key::Hyper,
Key::Named(NamedKey::Super) => bevy_input::keyboard::Key::Super,
Key::Named(NamedKey::Enter) => bevy_input::keyboard::Key::Enter,
Key::Named(NamedKey::Tab) => bevy_input::keyboard::Key::Tab,
Key::Named(NamedKey::Space) => bevy_input::keyboard::Key::Space,
Key::Named(NamedKey::ArrowDown) => bevy_input::keyboard::Key::ArrowDown,
Key::Named(NamedKey::ArrowLeft) => bevy_input::keyboard::Key::ArrowLeft,
Key::Named(NamedKey::ArrowRight) => bevy_input::keyboard::Key::ArrowRight,
Key::Named(NamedKey::ArrowUp) => bevy_input::keyboard::Key::ArrowUp,
Key::Named(NamedKey::End) => bevy_input::keyboard::Key::End,
Key::Named(NamedKey::Home) => bevy_input::keyboard::Key::Home,
Key::Named(NamedKey::PageDown) => bevy_input::keyboard::Key::PageDown,
Key::Named(NamedKey::PageUp) => bevy_input::keyboard::Key::PageUp,
Key::Named(NamedKey::Backspace) => bevy_input::keyboard::Key::Backspace,
Key::Named(NamedKey::Clear) => bevy_input::keyboard::Key::Clear,
Key::Named(NamedKey::Copy) => bevy_input::keyboard::Key::Copy,
Key::Named(NamedKey::CrSel) => bevy_input::keyboard::Key::CrSel,
Key::Named(NamedKey::Cut) => bevy_input::keyboard::Key::Cut,
Key::Named(NamedKey::Delete) => bevy_input::keyboard::Key::Delete,
Key::Named(NamedKey::EraseEof) => bevy_input::keyboard::Key::EraseEof,
Key::Named(NamedKey::ExSel) => bevy_input::keyboard::Key::ExSel,
Key::Named(NamedKey::Insert) => bevy_input::keyboard::Key::Insert,
Key::Named(NamedKey::Paste) => bevy_input::keyboard::Key::Paste,
Key::Named(NamedKey::Redo) => bevy_input::keyboard::Key::Redo,
Key::Named(NamedKey::Undo) => bevy_input::keyboard::Key::Undo,
Key::Named(NamedKey::Accept) => bevy_input::keyboard::Key::Accept,
Key::Named(NamedKey::Again) => bevy_input::keyboard::Key::Again,
Key::Named(NamedKey::Attn) => bevy_input::keyboard::Key::Attn,
Key::Named(NamedKey::Cancel) => bevy_input::keyboard::Key::Cancel,
Key::Named(NamedKey::ContextMenu) => bevy_input::keyboard::Key::ContextMenu,
Key::Named(NamedKey::Escape) => bevy_input::keyboard::Key::Escape,
Key::Named(NamedKey::Execute) => bevy_input::keyboard::Key::Execute,
Key::Named(NamedKey::Find) => bevy_input::keyboard::Key::Find,
Key::Named(NamedKey::Help) => bevy_input::keyboard::Key::Help,
Key::Named(NamedKey::Pause) => bevy_input::keyboard::Key::Pause,
Key::Named(NamedKey::Play) => bevy_input::keyboard::Key::Play,
Key::Named(NamedKey::Props) => bevy_input::keyboard::Key::Props,
Key::Named(NamedKey::Select) => bevy_input::keyboard::Key::Select,
Key::Named(NamedKey::ZoomIn) => bevy_input::keyboard::Key::ZoomIn,
Key::Named(NamedKey::ZoomOut) => bevy_input::keyboard::Key::ZoomOut,
Key::Named(NamedKey::BrightnessDown) => bevy_input::keyboard::Key::BrightnessDown,
Key::Named(NamedKey::BrightnessUp) => bevy_input::keyboard::Key::BrightnessUp,
Key::Named(NamedKey::Eject) => bevy_input::keyboard::Key::Eject,
Key::Named(NamedKey::LogOff) => bevy_input::keyboard::Key::LogOff,
Key::Named(NamedKey::Power) => bevy_input::keyboard::Key::Power,
Key::Named(NamedKey::PowerOff) => bevy_input::keyboard::Key::PowerOff,
Key::Named(NamedKey::PrintScreen) => bevy_input::keyboard::Key::PrintScreen,
Key::Named(NamedKey::Hibernate) => bevy_input::keyboard::Key::Hibernate,
Key::Named(NamedKey::Standby) => bevy_input::keyboard::Key::Standby,
Key::Named(NamedKey::WakeUp) => bevy_input::keyboard::Key::WakeUp,
Key::Named(NamedKey::AllCandidates) => bevy_input::keyboard::Key::AllCandidates,
Key::Named(NamedKey::Alphanumeric) => bevy_input::keyboard::Key::Alphanumeric,
Key::Named(NamedKey::CodeInput) => bevy_input::keyboard::Key::CodeInput,
Key::Named(NamedKey::Compose) => bevy_input::keyboard::Key::Compose,
Key::Named(NamedKey::Convert) => bevy_input::keyboard::Key::Convert,
Key::Named(NamedKey::FinalMode) => bevy_input::keyboard::Key::FinalMode,
Key::Named(NamedKey::GroupFirst) => bevy_input::keyboard::Key::GroupFirst,
Key::Named(NamedKey::GroupLast) => bevy_input::keyboard::Key::GroupLast,
Key::Named(NamedKey::GroupNext) => bevy_input::keyboard::Key::GroupNext,
Key::Named(NamedKey::GroupPrevious) => bevy_input::keyboard::Key::GroupPrevious,
Key::Named(NamedKey::ModeChange) => bevy_input::keyboard::Key::ModeChange,
Key::Named(NamedKey::NextCandidate) => bevy_input::keyboard::Key::NextCandidate,
Key::Named(NamedKey::NonConvert) => bevy_input::keyboard::Key::NonConvert,
Key::Named(NamedKey::PreviousCandidate) => bevy_input::keyboard::Key::PreviousCandidate,
Key::Named(NamedKey::Process) => bevy_input::keyboard::Key::Process,
Key::Named(NamedKey::SingleCandidate) => bevy_input::keyboard::Key::SingleCandidate,
Key::Named(NamedKey::HangulMode) => bevy_input::keyboard::Key::HangulMode,
Key::Named(NamedKey::HanjaMode) => bevy_input::keyboard::Key::HanjaMode,
Key::Named(NamedKey::JunjaMode) => bevy_input::keyboard::Key::JunjaMode,
Key::Named(NamedKey::Eisu) => bevy_input::keyboard::Key::Eisu,
Key::Named(NamedKey::Hankaku) => bevy_input::keyboard::Key::Hankaku,
Key::Named(NamedKey::Hiragana) => bevy_input::keyboard::Key::Hiragana,
Key::Named(NamedKey::HiraganaKatakana) => bevy_input::keyboard::Key::HiraganaKatakana,
Key::Named(NamedKey::KanaMode) => bevy_input::keyboard::Key::KanaMode,
Key::Named(NamedKey::KanjiMode) => bevy_input::keyboard::Key::KanjiMode,
Key::Named(NamedKey::Katakana) => bevy_input::keyboard::Key::Katakana,
Key::Named(NamedKey::Romaji) => bevy_input::keyboard::Key::Romaji,
Key::Named(NamedKey::Zenkaku) => bevy_input::keyboard::Key::Zenkaku,
Key::Named(NamedKey::ZenkakuHankaku) => bevy_input::keyboard::Key::ZenkakuHankaku,
Key::Named(NamedKey::Soft1) => bevy_input::keyboard::Key::Soft1,
Key::Named(NamedKey::Soft2) => bevy_input::keyboard::Key::Soft2,
Key::Named(NamedKey::Soft3) => bevy_input::keyboard::Key::Soft3,
Key::Named(NamedKey::Soft4) => bevy_input::keyboard::Key::Soft4,
Key::Named(NamedKey::ChannelDown) => bevy_input::keyboard::Key::ChannelDown,
Key::Named(NamedKey::ChannelUp) => bevy_input::keyboard::Key::ChannelUp,
Key::Named(NamedKey::Close) => bevy_input::keyboard::Key::Close,
Key::Named(NamedKey::MailForward) => bevy_input::keyboard::Key::MailForward,
Key::Named(NamedKey::MailReply) => bevy_input::keyboard::Key::MailReply,
Key::Named(NamedKey::MailSend) => bevy_input::keyboard::Key::MailSend,
Key::Named(NamedKey::MediaClose) => bevy_input::keyboard::Key::MediaClose,
Key::Named(NamedKey::MediaFastForward) => bevy_input::keyboard::Key::MediaFastForward,
Key::Named(NamedKey::MediaPause) => bevy_input::keyboard::Key::MediaPause,
Key::Named(NamedKey::MediaPlay) => bevy_input::keyboard::Key::MediaPlay,
Key::Named(NamedKey::MediaPlayPause) => bevy_input::keyboard::Key::MediaPlayPause,
Key::Named(NamedKey::MediaRecord) => bevy_input::keyboard::Key::MediaRecord,
Key::Named(NamedKey::MediaRewind) => bevy_input::keyboard::Key::MediaRewind,
Key::Named(NamedKey::MediaStop) => bevy_input::keyboard::Key::MediaStop,
Key::Named(NamedKey::MediaTrackNext) => bevy_input::keyboard::Key::MediaTrackNext,
Key::Named(NamedKey::MediaTrackPrevious) => bevy_input::keyboard::Key::MediaTrackPrevious,
Key::Named(NamedKey::New) => bevy_input::keyboard::Key::New,
Key::Named(NamedKey::Open) => bevy_input::keyboard::Key::Open,
Key::Named(NamedKey::Print) => bevy_input::keyboard::Key::Print,
Key::Named(NamedKey::Save) => bevy_input::keyboard::Key::Save,
Key::Named(NamedKey::SpellCheck) => bevy_input::keyboard::Key::SpellCheck,
Key::Named(NamedKey::Key11) => bevy_input::keyboard::Key::Key11,
Key::Named(NamedKey::Key12) => bevy_input::keyboard::Key::Key12,
Key::Named(NamedKey::AudioBalanceLeft) => bevy_input::keyboard::Key::AudioBalanceLeft,
Key::Named(NamedKey::AudioBalanceRight) => bevy_input::keyboard::Key::AudioBalanceRight,
Key::Named(NamedKey::AudioBassBoostDown) => bevy_input::keyboard::Key::AudioBassBoostDown,
Key::Named(NamedKey::AudioBassBoostToggle) => {
bevy_input::keyboard::Key::AudioBassBoostToggle
}
Key::Named(NamedKey::AudioBassBoostUp) => bevy_input::keyboard::Key::AudioBassBoostUp,
Key::Named(NamedKey::AudioFaderFront) => bevy_input::keyboard::Key::AudioFaderFront,
Key::Named(NamedKey::AudioFaderRear) => bevy_input::keyboard::Key::AudioFaderRear,
Key::Named(NamedKey::AudioSurroundModeNext) => {
bevy_input::keyboard::Key::AudioSurroundModeNext
}
Key::Named(NamedKey::AudioTrebleDown) => bevy_input::keyboard::Key::AudioTrebleDown,
Key::Named(NamedKey::AudioTrebleUp) => bevy_input::keyboard::Key::AudioTrebleUp,
Key::Named(NamedKey::AudioVolumeDown) => bevy_input::keyboard::Key::AudioVolumeDown,
Key::Named(NamedKey::AudioVolumeUp) => bevy_input::keyboard::Key::AudioVolumeUp,
Key::Named(NamedKey::AudioVolumeMute) => bevy_input::keyboard::Key::AudioVolumeMute,
Key::Named(NamedKey::MicrophoneToggle) => bevy_input::keyboard::Key::MicrophoneToggle,
Key::Named(NamedKey::MicrophoneVolumeDown) => {
bevy_input::keyboard::Key::MicrophoneVolumeDown
}
Key::Named(NamedKey::MicrophoneVolumeUp) => bevy_input::keyboard::Key::MicrophoneVolumeUp,
Key::Named(NamedKey::MicrophoneVolumeMute) => {
bevy_input::keyboard::Key::MicrophoneVolumeMute
}
Key::Named(NamedKey::SpeechCorrectionList) => {
bevy_input::keyboard::Key::SpeechCorrectionList
}
Key::Named(NamedKey::SpeechInputToggle) => bevy_input::keyboard::Key::SpeechInputToggle,
Key::Named(NamedKey::LaunchApplication1) => bevy_input::keyboard::Key::LaunchApplication1,
Key::Named(NamedKey::LaunchApplication2) => bevy_input::keyboard::Key::LaunchApplication2,
Key::Named(NamedKey::LaunchCalendar) => bevy_input::keyboard::Key::LaunchCalendar,
Key::Named(NamedKey::LaunchContacts) => bevy_input::keyboard::Key::LaunchContacts,
Key::Named(NamedKey::LaunchMail) => bevy_input::keyboard::Key::LaunchMail,
Key::Named(NamedKey::LaunchMediaPlayer) => bevy_input::keyboard::Key::LaunchMediaPlayer,
Key::Named(NamedKey::LaunchMusicPlayer) => bevy_input::keyboard::Key::LaunchMusicPlayer,
Key::Named(NamedKey::LaunchPhone) => bevy_input::keyboard::Key::LaunchPhone,
Key::Named(NamedKey::LaunchScreenSaver) => bevy_input::keyboard::Key::LaunchScreenSaver,
Key::Named(NamedKey::LaunchSpreadsheet) => bevy_input::keyboard::Key::LaunchSpreadsheet,
Key::Named(NamedKey::LaunchWebBrowser) => bevy_input::keyboard::Key::LaunchWebBrowser,
Key::Named(NamedKey::LaunchWebCam) => bevy_input::keyboard::Key::LaunchWebCam,
Key::Named(NamedKey::LaunchWordProcessor) => bevy_input::keyboard::Key::LaunchWordProcessor,
Key::Named(NamedKey::BrowserBack) => bevy_input::keyboard::Key::BrowserBack,
Key::Named(NamedKey::BrowserFavorites) => bevy_input::keyboard::Key::BrowserFavorites,
Key::Named(NamedKey::BrowserForward) => bevy_input::keyboard::Key::BrowserForward,
Key::Named(NamedKey::BrowserHome) => bevy_input::keyboard::Key::BrowserHome,
Key::Named(NamedKey::BrowserRefresh) => bevy_input::keyboard::Key::BrowserRefresh,
Key::Named(NamedKey::BrowserSearch) => bevy_input::keyboard::Key::BrowserSearch,
Key::Named(NamedKey::BrowserStop) => bevy_input::keyboard::Key::BrowserStop,
Key::Named(NamedKey::AppSwitch) => bevy_input::keyboard::Key::AppSwitch,
Key::Named(NamedKey::Call) => bevy_input::keyboard::Key::Call,
Key::Named(NamedKey::Camera) => bevy_input::keyboard::Key::Camera,
Key::Named(NamedKey::CameraFocus) => bevy_input::keyboard::Key::CameraFocus,
Key::Named(NamedKey::EndCall) => bevy_input::keyboard::Key::EndCall,
Key::Named(NamedKey::GoBack) => bevy_input::keyboard::Key::GoBack,
Key::Named(NamedKey::GoHome) => bevy_input::keyboard::Key::GoHome,
Key::Named(NamedKey::HeadsetHook) => bevy_input::keyboard::Key::HeadsetHook,
Key::Named(NamedKey::LastNumberRedial) => bevy_input::keyboard::Key::LastNumberRedial,
Key::Named(NamedKey::Notification) => bevy_input::keyboard::Key::Notification,
Key::Named(NamedKey::MannerMode) => bevy_input::keyboard::Key::MannerMode,
Key::Named(NamedKey::VoiceDial) => bevy_input::keyboard::Key::VoiceDial,
Key::Named(NamedKey::TV) => bevy_input::keyboard::Key::TV,
Key::Named(NamedKey::TV3DMode) => bevy_input::keyboard::Key::TV3DMode,
Key::Named(NamedKey::TVAntennaCable) => bevy_input::keyboard::Key::TVAntennaCable,
Key::Named(NamedKey::TVAudioDescription) => bevy_input::keyboard::Key::TVAudioDescription,
Key::Named(NamedKey::TVAudioDescriptionMixDown) => {
bevy_input::keyboard::Key::TVAudioDescriptionMixDown
}
Key::Named(NamedKey::TVAudioDescriptionMixUp) => {
bevy_input::keyboard::Key::TVAudioDescriptionMixUp
}
Key::Named(NamedKey::TVContentsMenu) => bevy_input::keyboard::Key::TVContentsMenu,
Key::Named(NamedKey::TVDataService) => bevy_input::keyboard::Key::TVDataService,
Key::Named(NamedKey::TVInput) => bevy_input::keyboard::Key::TVInput,
Key::Named(NamedKey::TVInputComponent1) => bevy_input::keyboard::Key::TVInputComponent1,
Key::Named(NamedKey::TVInputComponent2) => bevy_input::keyboard::Key::TVInputComponent2,
Key::Named(NamedKey::TVInputComposite1) => bevy_input::keyboard::Key::TVInputComposite1,
Key::Named(NamedKey::TVInputComposite2) => bevy_input::keyboard::Key::TVInputComposite2,
Key::Named(NamedKey::TVInputHDMI1) => bevy_input::keyboard::Key::TVInputHDMI1,
Key::Named(NamedKey::TVInputHDMI2) => bevy_input::keyboard::Key::TVInputHDMI2,
Key::Named(NamedKey::TVInputHDMI3) => bevy_input::keyboard::Key::TVInputHDMI3,
Key::Named(NamedKey::TVInputHDMI4) => bevy_input::keyboard::Key::TVInputHDMI4,
Key::Named(NamedKey::TVInputVGA1) => bevy_input::keyboard::Key::TVInputVGA1,
Key::Named(NamedKey::TVMediaContext) => bevy_input::keyboard::Key::TVMediaContext,
Key::Named(NamedKey::TVNetwork) => bevy_input::keyboard::Key::TVNetwork,
Key::Named(NamedKey::TVNumberEntry) => bevy_input::keyboard::Key::TVNumberEntry,
Key::Named(NamedKey::TVPower) => bevy_input::keyboard::Key::TVPower,
Key::Named(NamedKey::TVRadioService) => bevy_input::keyboard::Key::TVRadioService,
Key::Named(NamedKey::TVSatellite) => bevy_input::keyboard::Key::TVSatellite,
Key::Named(NamedKey::TVSatelliteBS) => bevy_input::keyboard::Key::TVSatelliteBS,
Key::Named(NamedKey::TVSatelliteCS) => bevy_input::keyboard::Key::TVSatelliteCS,
Key::Named(NamedKey::TVSatelliteToggle) => bevy_input::keyboard::Key::TVSatelliteToggle,
Key::Named(NamedKey::TVTerrestrialAnalog) => bevy_input::keyboard::Key::TVTerrestrialAnalog,
Key::Named(NamedKey::TVTerrestrialDigital) => {
bevy_input::keyboard::Key::TVTerrestrialDigital
}
Key::Named(NamedKey::TVTimer) => bevy_input::keyboard::Key::TVTimer,
Key::Named(NamedKey::AVRInput) => bevy_input::keyboard::Key::AVRInput,
Key::Named(NamedKey::AVRPower) => bevy_input::keyboard::Key::AVRPower,
Key::Named(NamedKey::ColorF0Red) => bevy_input::keyboard::Key::ColorF0Red,
Key::Named(NamedKey::ColorF1Green) => bevy_input::keyboard::Key::ColorF1Green,
Key::Named(NamedKey::ColorF2Yellow) => bevy_input::keyboard::Key::ColorF2Yellow,
Key::Named(NamedKey::ColorF3Blue) => bevy_input::keyboard::Key::ColorF3Blue,
Key::Named(NamedKey::ColorF4Grey) => bevy_input::keyboard::Key::ColorF4Grey,
Key::Named(NamedKey::ColorF5Brown) => bevy_input::keyboard::Key::ColorF5Brown,
Key::Named(NamedKey::ClosedCaptionToggle) => bevy_input::keyboard::Key::ClosedCaptionToggle,
Key::Named(NamedKey::Dimmer) => bevy_input::keyboard::Key::Dimmer,
Key::Named(NamedKey::DisplaySwap) => bevy_input::keyboard::Key::DisplaySwap,
Key::Named(NamedKey::DVR) => bevy_input::keyboard::Key::DVR,
Key::Named(NamedKey::Exit) => bevy_input::keyboard::Key::Exit,
Key::Named(NamedKey::FavoriteClear0) => bevy_input::keyboard::Key::FavoriteClear0,
Key::Named(NamedKey::FavoriteClear1) => bevy_input::keyboard::Key::FavoriteClear1,
Key::Named(NamedKey::FavoriteClear2) => bevy_input::keyboard::Key::FavoriteClear2,
Key::Named(NamedKey::FavoriteClear3) => bevy_input::keyboard::Key::FavoriteClear3,
Key::Named(NamedKey::FavoriteRecall0) => bevy_input::keyboard::Key::FavoriteRecall0,
Key::Named(NamedKey::FavoriteRecall1) => bevy_input::keyboard::Key::FavoriteRecall1,
Key::Named(NamedKey::FavoriteRecall2) => bevy_input::keyboard::Key::FavoriteRecall2,
Key::Named(NamedKey::FavoriteRecall3) => bevy_input::keyboard::Key::FavoriteRecall3,
Key::Named(NamedKey::FavoriteStore0) => bevy_input::keyboard::Key::FavoriteStore0,
Key::Named(NamedKey::FavoriteStore1) => bevy_input::keyboard::Key::FavoriteStore1,
Key::Named(NamedKey::FavoriteStore2) => bevy_input::keyboard::Key::FavoriteStore2,
Key::Named(NamedKey::FavoriteStore3) => bevy_input::keyboard::Key::FavoriteStore3,
Key::Named(NamedKey::Guide) => bevy_input::keyboard::Key::Guide,
Key::Named(NamedKey::GuideNextDay) => bevy_input::keyboard::Key::GuideNextDay,
Key::Named(NamedKey::GuidePreviousDay) => bevy_input::keyboard::Key::GuidePreviousDay,
Key::Named(NamedKey::Info) => bevy_input::keyboard::Key::Info,
Key::Named(NamedKey::InstantReplay) => bevy_input::keyboard::Key::InstantReplay,
Key::Named(NamedKey::Link) => bevy_input::keyboard::Key::Link,
Key::Named(NamedKey::ListProgram) => bevy_input::keyboard::Key::ListProgram,
Key::Named(NamedKey::LiveContent) => bevy_input::keyboard::Key::LiveContent,
Key::Named(NamedKey::Lock) => bevy_input::keyboard::Key::Lock,
Key::Named(NamedKey::MediaApps) => bevy_input::keyboard::Key::MediaApps,
Key::Named(NamedKey::MediaAudioTrack) => bevy_input::keyboard::Key::MediaAudioTrack,
Key::Named(NamedKey::MediaLast) => bevy_input::keyboard::Key::MediaLast,
Key::Named(NamedKey::MediaSkipBackward) => bevy_input::keyboard::Key::MediaSkipBackward,
Key::Named(NamedKey::MediaSkipForward) => bevy_input::keyboard::Key::MediaSkipForward,
Key::Named(NamedKey::MediaStepBackward) => bevy_input::keyboard::Key::MediaStepBackward,
Key::Named(NamedKey::MediaStepForward) => bevy_input::keyboard::Key::MediaStepForward,
Key::Named(NamedKey::MediaTopMenu) => bevy_input::keyboard::Key::MediaTopMenu,
Key::Named(NamedKey::NavigateIn) => bevy_input::keyboard::Key::NavigateIn,
Key::Named(NamedKey::NavigateNext) => bevy_input::keyboard::Key::NavigateNext,
Key::Named(NamedKey::NavigateOut) => bevy_input::keyboard::Key::NavigateOut,
Key::Named(NamedKey::NavigatePrevious) => bevy_input::keyboard::Key::NavigatePrevious,
Key::Named(NamedKey::NextFavoriteChannel) => bevy_input::keyboard::Key::NextFavoriteChannel,
Key::Named(NamedKey::NextUserProfile) => bevy_input::keyboard::Key::NextUserProfile,
Key::Named(NamedKey::OnDemand) => bevy_input::keyboard::Key::OnDemand,
Key::Named(NamedKey::Pairing) => bevy_input::keyboard::Key::Pairing,
Key::Named(NamedKey::PinPDown) => bevy_input::keyboard::Key::PinPDown,
Key::Named(NamedKey::PinPMove) => bevy_input::keyboard::Key::PinPMove,
Key::Named(NamedKey::PinPToggle) => bevy_input::keyboard::Key::PinPToggle,
Key::Named(NamedKey::PinPUp) => bevy_input::keyboard::Key::PinPUp,
Key::Named(NamedKey::PlaySpeedDown) => bevy_input::keyboard::Key::PlaySpeedDown,
Key::Named(NamedKey::PlaySpeedReset) => bevy_input::keyboard::Key::PlaySpeedReset,
Key::Named(NamedKey::PlaySpeedUp) => bevy_input::keyboard::Key::PlaySpeedUp,
Key::Named(NamedKey::RandomToggle) => bevy_input::keyboard::Key::RandomToggle,
Key::Named(NamedKey::RcLowBattery) => bevy_input::keyboard::Key::RcLowBattery,
Key::Named(NamedKey::RecordSpeedNext) => bevy_input::keyboard::Key::RecordSpeedNext,
Key::Named(NamedKey::RfBypass) => bevy_input::keyboard::Key::RfBypass,
Key::Named(NamedKey::ScanChannelsToggle) => bevy_input::keyboard::Key::ScanChannelsToggle,
Key::Named(NamedKey::ScreenModeNext) => bevy_input::keyboard::Key::ScreenModeNext,
Key::Named(NamedKey::Settings) => bevy_input::keyboard::Key::Settings,
Key::Named(NamedKey::SplitScreenToggle) => bevy_input::keyboard::Key::SplitScreenToggle,
Key::Named(NamedKey::STBInput) => bevy_input::keyboard::Key::STBInput,
Key::Named(NamedKey::STBPower) => bevy_input::keyboard::Key::STBPower,
Key::Named(NamedKey::Subtitle) => bevy_input::keyboard::Key::Subtitle,
Key::Named(NamedKey::Teletext) => bevy_input::keyboard::Key::Teletext,
Key::Named(NamedKey::VideoModeNext) => bevy_input::keyboard::Key::VideoModeNext,
Key::Named(NamedKey::Wink) => bevy_input::keyboard::Key::Wink,
Key::Named(NamedKey::ZoomToggle) => bevy_input::keyboard::Key::ZoomToggle,
Key::Named(NamedKey::F1) => bevy_input::keyboard::Key::F1,
Key::Named(NamedKey::F2) => bevy_input::keyboard::Key::F2,
Key::Named(NamedKey::F3) => bevy_input::keyboard::Key::F3,
Key::Named(NamedKey::F4) => bevy_input::keyboard::Key::F4,
Key::Named(NamedKey::F5) => bevy_input::keyboard::Key::F5,
Key::Named(NamedKey::F6) => bevy_input::keyboard::Key::F6,
Key::Named(NamedKey::F7) => bevy_input::keyboard::Key::F7,
Key::Named(NamedKey::F8) => bevy_input::keyboard::Key::F8,
Key::Named(NamedKey::F9) => bevy_input::keyboard::Key::F9,
Key::Named(NamedKey::F10) => bevy_input::keyboard::Key::F10,
Key::Named(NamedKey::F11) => bevy_input::keyboard::Key::F11,
Key::Named(NamedKey::F12) => bevy_input::keyboard::Key::F12,
Key::Named(NamedKey::F13) => bevy_input::keyboard::Key::F13,
Key::Named(NamedKey::F14) => bevy_input::keyboard::Key::F14,
Key::Named(NamedKey::F15) => bevy_input::keyboard::Key::F15,
Key::Named(NamedKey::F16) => bevy_input::keyboard::Key::F16,
Key::Named(NamedKey::F17) => bevy_input::keyboard::Key::F17,
Key::Named(NamedKey::F18) => bevy_input::keyboard::Key::F18,
Key::Named(NamedKey::F19) => bevy_input::keyboard::Key::F19,
Key::Named(NamedKey::F20) => bevy_input::keyboard::Key::F20,
Key::Named(NamedKey::F21) => bevy_input::keyboard::Key::F21,
Key::Named(NamedKey::F22) => bevy_input::keyboard::Key::F22,
Key::Named(NamedKey::F23) => bevy_input::keyboard::Key::F23,
Key::Named(NamedKey::F24) => bevy_input::keyboard::Key::F24,
Key::Named(NamedKey::F25) => bevy_input::keyboard::Key::F25,
Key::Named(NamedKey::F26) => bevy_input::keyboard::Key::F26,
Key::Named(NamedKey::F27) => bevy_input::keyboard::Key::F27,
Key::Named(NamedKey::F28) => bevy_input::keyboard::Key::F28,
Key::Named(NamedKey::F29) => bevy_input::keyboard::Key::F29,
Key::Named(NamedKey::F30) => bevy_input::keyboard::Key::F30,
Key::Named(NamedKey::F31) => bevy_input::keyboard::Key::F31,
Key::Named(NamedKey::F32) => bevy_input::keyboard::Key::F32,
Key::Named(NamedKey::F33) => bevy_input::keyboard::Key::F33,
Key::Named(NamedKey::F34) => bevy_input::keyboard::Key::F34,
Key::Named(NamedKey::F35) => bevy_input::keyboard::Key::F35,
_ => todo!(),
}
}
pub fn convert_native_key(native_key: &NativeKey) -> bevy_input::keyboard::NativeKey {
match native_key {
NativeKey::Unidentified => bevy_input::keyboard::NativeKey::Unidentified,
NativeKey::Android(v) => bevy_input::keyboard::NativeKey::Android(*v),
NativeKey::MacOS(v) => bevy_input::keyboard::NativeKey::MacOS(*v),
NativeKey::Windows(v) => bevy_input::keyboard::NativeKey::Windows(*v),
NativeKey::Xkb(v) => bevy_input::keyboard::NativeKey::Xkb(*v),
NativeKey::Web(v) => bevy_input::keyboard::NativeKey::Web(v.clone()),
}
}
/// Converts a [`SystemCursorIcon`] to a [`winit::window::CursorIcon`].
pub fn convert_system_cursor_icon(cursor_icon: SystemCursorIcon) -> winit::window::CursorIcon {
match cursor_icon {
SystemCursorIcon::Crosshair => winit::window::CursorIcon::Crosshair,
SystemCursorIcon::Pointer => winit::window::CursorIcon::Pointer,
SystemCursorIcon::Move => winit::window::CursorIcon::Move,
SystemCursorIcon::Text => winit::window::CursorIcon::Text,
SystemCursorIcon::Wait => winit::window::CursorIcon::Wait,
SystemCursorIcon::Help => winit::window::CursorIcon::Help,
SystemCursorIcon::Progress => winit::window::CursorIcon::Progress,
SystemCursorIcon::NotAllowed => winit::window::CursorIcon::NotAllowed,
SystemCursorIcon::ContextMenu => winit::window::CursorIcon::ContextMenu,
SystemCursorIcon::Cell => winit::window::CursorIcon::Cell,
SystemCursorIcon::VerticalText => winit::window::CursorIcon::VerticalText,
SystemCursorIcon::Alias => winit::window::CursorIcon::Alias,
SystemCursorIcon::Copy => winit::window::CursorIcon::Copy,
SystemCursorIcon::NoDrop => winit::window::CursorIcon::NoDrop,
SystemCursorIcon::Grab => winit::window::CursorIcon::Grab,
SystemCursorIcon::Grabbing => winit::window::CursorIcon::Grabbing,
SystemCursorIcon::AllScroll => winit::window::CursorIcon::AllScroll,
SystemCursorIcon::ZoomIn => winit::window::CursorIcon::ZoomIn,
SystemCursorIcon::ZoomOut => winit::window::CursorIcon::ZoomOut,
SystemCursorIcon::EResize => winit::window::CursorIcon::EResize,
SystemCursorIcon::NResize => winit::window::CursorIcon::NResize,
SystemCursorIcon::NeResize => winit::window::CursorIcon::NeResize,
SystemCursorIcon::NwResize => winit::window::CursorIcon::NwResize,
SystemCursorIcon::SResize => winit::window::CursorIcon::SResize,
SystemCursorIcon::SeResize => winit::window::CursorIcon::SeResize,
SystemCursorIcon::SwResize => winit::window::CursorIcon::SwResize,
SystemCursorIcon::WResize => winit::window::CursorIcon::WResize,
SystemCursorIcon::EwResize => winit::window::CursorIcon::EwResize,
SystemCursorIcon::NsResize => winit::window::CursorIcon::NsResize,
SystemCursorIcon::NeswResize => winit::window::CursorIcon::NeswResize,
SystemCursorIcon::NwseResize => winit::window::CursorIcon::NwseResize,
SystemCursorIcon::ColResize => winit::window::CursorIcon::ColResize,
SystemCursorIcon::RowResize => winit::window::CursorIcon::RowResize,
_ => winit::window::CursorIcon::Default,
}
}
pub fn convert_window_level(window_level: WindowLevel) -> winit::window::WindowLevel {
match window_level {
WindowLevel::AlwaysOnBottom => winit::window::WindowLevel::AlwaysOnBottom,
WindowLevel::Normal => winit::window::WindowLevel::Normal,
WindowLevel::AlwaysOnTop => winit::window::WindowLevel::AlwaysOnTop,
}
}
pub fn convert_winit_theme(theme: winit::window::Theme) -> WindowTheme {
match theme {
winit::window::Theme::Light => WindowTheme::Light,
winit::window::Theme::Dark => WindowTheme::Dark,
}
}
pub fn convert_window_theme(theme: WindowTheme) -> winit::window::Theme {
match theme {
WindowTheme::Light => winit::window::Theme::Light,
WindowTheme::Dark => winit::window::Theme::Dark,
}
}
pub fn convert_enabled_buttons(enabled_buttons: EnabledButtons) -> winit::window::WindowButtons {
let mut window_buttons = winit::window::WindowButtons::empty();
if enabled_buttons.minimize {
window_buttons.insert(winit::window::WindowButtons::MINIMIZE);
}
if enabled_buttons.maximize {
window_buttons.insert(winit::window::WindowButtons::MAXIMIZE);
}
if enabled_buttons.close {
window_buttons.insert(winit::window::WindowButtons::CLOSE);
}
window_buttons
}
pub fn convert_resize_direction(resize_direction: CompassOctant) -> winit::window::ResizeDirection {
match resize_direction {
CompassOctant::West => winit::window::ResizeDirection::West,
CompassOctant::North => winit::window::ResizeDirection::North,
CompassOctant::East => winit::window::ResizeDirection::East,
CompassOctant::South => winit::window::ResizeDirection::South,
CompassOctant::NorthWest => winit::window::ResizeDirection::NorthWest,
CompassOctant::NorthEast => winit::window::ResizeDirection::NorthEast,
CompassOctant::SouthWest => winit::window::ResizeDirection::SouthWest,
CompassOctant::SouthEast => winit::window::ResizeDirection::SouthEast,
}
}

201
vendor/bevy_winit/src/cursor.rs vendored Normal file
View File

@@ -0,0 +1,201 @@
//! Components to customize winit cursor
use crate::{
converters::convert_system_cursor_icon,
state::{CursorSource, PendingCursor},
};
#[cfg(feature = "custom_cursor")]
use crate::{
custom_cursor::{
calculate_effective_rect, extract_and_transform_rgba_pixels, extract_rgba_pixels,
transform_hotspot, CustomCursorPlugin,
},
state::{CustomCursorCache, CustomCursorCacheKey},
WinitCustomCursor,
};
use bevy_app::{App, Last, Plugin};
#[cfg(feature = "custom_cursor")]
use bevy_asset::Assets;
#[cfg(feature = "custom_cursor")]
use bevy_ecs::system::Res;
use bevy_ecs::{
change_detection::DetectChanges,
component::Component,
entity::Entity,
observer::Trigger,
query::With,
reflect::ReflectComponent,
system::{Commands, Local, Query},
world::{OnRemove, Ref},
};
#[cfg(feature = "custom_cursor")]
use bevy_image::{Image, TextureAtlasLayout};
use bevy_platform::collections::HashSet;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_window::{SystemCursorIcon, Window};
#[cfg(feature = "custom_cursor")]
use tracing::warn;
#[cfg(feature = "custom_cursor")]
pub use crate::custom_cursor::{CustomCursor, CustomCursorImage};
#[cfg(all(
feature = "custom_cursor",
target_family = "wasm",
target_os = "unknown"
))]
pub use crate::custom_cursor::CustomCursorUrl;
pub(crate) struct CursorPlugin;
impl Plugin for CursorPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "custom_cursor")]
app.add_plugins(CustomCursorPlugin);
app.register_type::<CursorIcon>()
.add_systems(Last, update_cursors);
app.add_observer(on_remove_cursor_icon);
}
}
/// Insert into a window entity to set the cursor for that window.
#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)]
#[reflect(Component, Debug, Default, PartialEq, Clone)]
pub enum CursorIcon {
#[cfg(feature = "custom_cursor")]
/// Custom cursor image.
Custom(CustomCursor),
/// System provided cursor icon.
System(SystemCursorIcon),
}
impl Default for CursorIcon {
fn default() -> Self {
CursorIcon::System(Default::default())
}
}
impl From<SystemCursorIcon> for CursorIcon {
fn from(icon: SystemCursorIcon) -> Self {
CursorIcon::System(icon)
}
}
fn update_cursors(
mut commands: Commands,
windows: Query<(Entity, Ref<CursorIcon>), With<Window>>,
#[cfg(feature = "custom_cursor")] cursor_cache: Res<CustomCursorCache>,
#[cfg(feature = "custom_cursor")] images: Res<Assets<Image>>,
#[cfg(feature = "custom_cursor")] texture_atlases: Res<Assets<TextureAtlasLayout>>,
mut queue: Local<HashSet<Entity>>,
) {
for (entity, cursor) in windows.iter() {
if !(queue.remove(&entity) || cursor.is_changed()) {
continue;
}
let cursor_source = match cursor.as_ref() {
#[cfg(feature = "custom_cursor")]
CursorIcon::Custom(CustomCursor::Image(c)) => {
let CustomCursorImage {
handle,
texture_atlas,
flip_x,
flip_y,
rect,
hotspot,
} = c;
let cache_key = CustomCursorCacheKey::Image {
id: handle.id(),
texture_atlas_layout_id: texture_atlas.as_ref().map(|a| a.layout.id()),
texture_atlas_index: texture_atlas.as_ref().map(|a| a.index),
flip_x: *flip_x,
flip_y: *flip_y,
rect: *rect,
};
if cursor_cache.0.contains_key(&cache_key) {
CursorSource::CustomCached(cache_key)
} else {
let Some(image) = images.get(handle) else {
warn!(
"Cursor image {handle:?} is not loaded yet and couldn't be used. Trying again next frame."
);
queue.insert(entity);
continue;
};
let (rect, needs_sub_image) =
calculate_effective_rect(&texture_atlases, image, texture_atlas, rect);
let (maybe_rgba, hotspot) = if *flip_x || *flip_y || needs_sub_image {
(
extract_and_transform_rgba_pixels(image, *flip_x, *flip_y, rect),
transform_hotspot(*hotspot, *flip_x, *flip_y, rect),
)
} else {
(extract_rgba_pixels(image), *hotspot)
};
let Some(rgba) = maybe_rgba else {
warn!("Cursor image {handle:?} not accepted because it's not rgba8 or rgba32float format");
continue;
};
let source = match WinitCustomCursor::from_rgba(
rgba,
rect.width() as u16,
rect.height() as u16,
hotspot.0,
hotspot.1,
) {
Ok(source) => source,
Err(err) => {
warn!("Cursor image {handle:?} is invalid: {err}");
continue;
}
};
CursorSource::Custom((cache_key, source))
}
}
#[cfg(all(
feature = "custom_cursor",
target_family = "wasm",
target_os = "unknown"
))]
CursorIcon::Custom(CustomCursor::Url(c)) => {
let cache_key = CustomCursorCacheKey::Url(c.url.clone());
if cursor_cache.0.contains_key(&cache_key) {
CursorSource::CustomCached(cache_key)
} else {
use crate::CustomCursorExtWebSys;
let source =
WinitCustomCursor::from_url(c.url.clone(), c.hotspot.0, c.hotspot.1);
CursorSource::Custom((cache_key, source))
}
}
CursorIcon::System(system_cursor_icon) => {
CursorSource::System(convert_system_cursor_icon(*system_cursor_icon))
}
};
commands
.entity(entity)
.insert(PendingCursor(Some(cursor_source)));
}
}
/// Resets the cursor to the default icon when `CursorIcon` is removed.
fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: Commands) {
// Use `try_insert` to avoid panic if the window is being destroyed.
commands
.entity(trigger.target())
.try_insert(PendingCursor(Some(CursorSource::System(
convert_system_cursor_icon(SystemCursorIcon::Default),
))));
}

605
vendor/bevy_winit/src/custom_cursor.rs vendored Normal file
View File

@@ -0,0 +1,605 @@
use bevy_app::{App, Plugin};
use bevy_asset::{Assets, Handle};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasPlugin};
use bevy_math::{ops, Rect, URect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use wgpu_types::TextureFormat;
use crate::{cursor::CursorIcon, state::CustomCursorCache};
/// A custom cursor created from an image.
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
#[reflect(Debug, Default, Hash, PartialEq, Clone)]
pub struct CustomCursorImage {
/// Handle to the image to use as the cursor. The image must be in 8 bit int
/// or 32 bit float rgba. PNG images work well for this.
pub handle: Handle<Image>,
/// An optional texture atlas used to render the image.
pub texture_atlas: Option<TextureAtlas>,
/// Whether the image should be flipped along its x-axis.
///
/// If true, the cursor's `hotspot` automatically flips along with the
/// image.
pub flip_x: bool,
/// Whether the image should be flipped along its y-axis.
///
/// If true, the cursor's `hotspot` automatically flips along with the
/// image.
pub flip_y: bool,
/// An optional rectangle representing the region of the image to render,
/// instead of rendering the full image. This is an easy one-off alternative
/// to using a [`TextureAtlas`].
///
/// When used with a [`TextureAtlas`], the rect is offset by the atlas's
/// minimal (top-left) corner position.
pub rect: Option<URect>,
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
/// the image bounds.
///
/// If you are flipping the image using `flip_x` or `flip_y`, you don't need
/// to adjust this field to account for the flip because it is adjusted
/// automatically.
pub hotspot: (u16, u16),
}
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
/// A custom cursor created from a URL.
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
#[reflect(Debug, Default, Hash, PartialEq, Clone)]
pub struct CustomCursorUrl {
/// Web URL to an image to use as the cursor. PNGs are preferred. Cursor
/// creation can fail if the image is invalid or not reachable.
pub url: String,
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
/// the image bounds.
pub hotspot: (u16, u16),
}
/// Custom cursor image data.
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)]
#[reflect(Clone, PartialEq, Hash)]
pub enum CustomCursor {
/// Use an image as the cursor.
Image(CustomCursorImage),
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
/// Use a URL to an image as the cursor.
Url(CustomCursorUrl),
}
impl From<CustomCursor> for CursorIcon {
fn from(cursor: CustomCursor) -> Self {
CursorIcon::Custom(cursor)
}
}
/// Adds support for custom cursors.
pub(crate) struct CustomCursorPlugin;
impl Plugin for CustomCursorPlugin {
fn build(&self, app: &mut App) {
if !app.is_plugin_added::<TextureAtlasPlugin>() {
app.add_plugins(TextureAtlasPlugin);
}
app.init_resource::<CustomCursorCache>();
}
}
/// Determines the effective rect and returns it along with a flag to indicate
/// whether a sub-image operation is needed. The flag allows the caller to
/// determine whether the image data needs a sub-image extracted from it. Note:
/// To avoid lossy comparisons between [`Rect`] and [`URect`], the flag is
/// always set to `true` when a [`TextureAtlas`] is used.
#[inline(always)]
pub(crate) fn calculate_effective_rect(
texture_atlas_layouts: &Assets<TextureAtlasLayout>,
image: &Image,
texture_atlas: &Option<TextureAtlas>,
rect: &Option<URect>,
) -> (Rect, bool) {
let atlas_rect = texture_atlas
.as_ref()
.and_then(|s| s.texture_rect(texture_atlas_layouts))
.map(|r| r.as_rect());
match (atlas_rect, rect) {
(None, None) => (
Rect {
min: Vec2::ZERO,
max: Vec2::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
),
},
false,
),
(None, Some(image_rect)) => (
image_rect.as_rect(),
image_rect
!= &URect {
min: UVec2::ZERO,
max: UVec2::new(
image.texture_descriptor.size.width,
image.texture_descriptor.size.height,
),
},
),
(Some(atlas_rect), None) => (atlas_rect, true),
(Some(atlas_rect), Some(image_rect)) => (
{
let mut image_rect = image_rect.as_rect();
image_rect.min += atlas_rect.min;
image_rect.max += atlas_rect.min;
image_rect
},
true,
),
}
}
/// Extracts the RGBA pixel data from `image`, converting it if necessary.
///
/// Only supports rgba8 and rgba32float formats.
pub(crate) fn extract_rgba_pixels(image: &Image) -> Option<Vec<u8>> {
match image.texture_descriptor.format {
TextureFormat::Rgba8Unorm
| TextureFormat::Rgba8UnormSrgb
| TextureFormat::Rgba8Snorm
| TextureFormat::Rgba8Uint
| TextureFormat::Rgba8Sint => Some(image.data.clone()?),
TextureFormat::Rgba32Float => image.data.as_ref().map(|data| {
data.chunks(4)
.map(|chunk| {
let chunk = chunk.try_into().unwrap();
let num = bytemuck::cast_ref::<[u8; 4], f32>(chunk);
ops::round(num.clamp(0.0, 1.0) * 255.0) as u8
})
.collect()
}),
_ => None,
}
}
/// Returns the `image` data as a `Vec<u8>` for the specified sub-region.
///
/// The image is flipped along the x and y axes if `flip_x` and `flip_y` are
/// `true`, respectively.
///
/// Only supports rgba8 and rgba32float formats.
pub(crate) fn extract_and_transform_rgba_pixels(
image: &Image,
flip_x: bool,
flip_y: bool,
rect: Rect,
) -> Option<Vec<u8>> {
let image_data = extract_rgba_pixels(image)?;
let width = rect.width() as usize;
let height = rect.height() as usize;
let mut sub_image_data = Vec::with_capacity(width * height * 4); // assuming 4 bytes per pixel (RGBA8)
for y in 0..height {
for x in 0..width {
let src_x = if flip_x { width - 1 - x } else { x };
let src_y = if flip_y { height - 1 - y } else { y };
let index = ((rect.min.y as usize + src_y)
* image.texture_descriptor.size.width as usize
+ (rect.min.x as usize + src_x))
* 4;
sub_image_data.extend_from_slice(&image_data[index..index + 4]);
}
}
Some(sub_image_data)
}
/// Transforms the `hotspot` coordinates based on whether the image is flipped
/// or not. The `rect` is used to determine the image's dimensions.
pub(crate) fn transform_hotspot(
hotspot: (u16, u16),
flip_x: bool,
flip_y: bool,
rect: Rect,
) -> (u16, u16) {
let hotspot_x = hotspot.0 as f32;
let hotspot_y = hotspot.1 as f32;
let (width, height) = (rect.width(), rect.height());
let hotspot_x = if flip_x {
(width - 1.0).max(0.0) - hotspot_x
} else {
hotspot_x
};
let hotspot_y = if flip_y {
(height - 1.0).max(0.0) - hotspot_y
} else {
hotspot_y
};
(hotspot_x as u16, hotspot_y as u16)
}
#[cfg(test)]
mod tests {
use bevy_app::App;
use bevy_asset::RenderAssetUsages;
use bevy_image::Image;
use bevy_math::Rect;
use bevy_math::Vec2;
use wgpu_types::{Extent3d, TextureDimension};
use super::*;
fn create_image_rgba8(data: &[u8]) -> Image {
Image::new(
Extent3d {
width: 3,
height: 3,
depth_or_array_layers: 1,
},
TextureDimension::D2,
data.to_vec(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::default(),
)
}
macro_rules! test_calculate_effective_rect {
($name:ident, $use_texture_atlas:expr, $rect:expr, $expected_rect:expr, $expected_needs_sub_image:expr) => {
#[test]
fn $name() {
let mut app = App::new();
let mut texture_atlas_layout_assets = Assets::<TextureAtlasLayout>::default();
// Create a simple 3x3 texture atlas layout for the test cases
// that use a texture atlas. In the future we could adjust the
// test cases to use different texture atlas layouts.
let layout = TextureAtlasLayout::from_grid(UVec2::new(3, 3), 1, 1, None, None);
let layout_handle = texture_atlas_layout_assets.add(layout);
app.insert_resource(texture_atlas_layout_assets);
let texture_atlases = app
.world()
.get_resource::<Assets<TextureAtlasLayout>>()
.unwrap();
let image = create_image_rgba8(&[0; 3 * 3 * 4]); // 3x3 image
let texture_atlas = if $use_texture_atlas {
Some(TextureAtlas::from(layout_handle))
} else {
None
};
let rect = $rect;
let (result_rect, needs_sub_image) =
calculate_effective_rect(&texture_atlases, &image, &texture_atlas, &rect);
assert_eq!(result_rect, $expected_rect);
assert_eq!(needs_sub_image, $expected_needs_sub_image);
}
};
}
test_calculate_effective_rect!(
no_texture_atlas_no_rect,
false,
None,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
false
);
test_calculate_effective_rect!(
no_texture_atlas_with_partial_rect,
false,
Some(URect {
min: UVec2::new(1, 1),
max: UVec2::new(3, 3)
}),
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
true
);
test_calculate_effective_rect!(
no_texture_atlas_with_full_rect,
false,
Some(URect {
min: UVec2::ZERO,
max: UVec2::new(3, 3)
}),
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
false
);
test_calculate_effective_rect!(
texture_atlas_no_rect,
true,
None,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
true // always needs sub-image to avoid comparing Rect against URect
);
test_calculate_effective_rect!(
texture_atlas_rect,
true,
Some(URect {
min: UVec2::ZERO,
max: UVec2::new(3, 3)
}),
Rect {
min: Vec2::new(0.0, 0.0),
max: Vec2::new(3.0, 3.0)
},
true // always needs sub-image to avoid comparing Rect against URect
);
fn create_image_rgba32float(data: &[u8]) -> Image {
let float_data: Vec<f32> = data
.chunks(4)
.flat_map(|chunk| {
chunk
.iter()
.map(|&x| x as f32 / 255.0) // convert each channel to f32
.collect::<Vec<f32>>()
})
.collect();
Image::new(
Extent3d {
width: 3,
height: 3,
depth_or_array_layers: 1,
},
TextureDimension::D2,
bytemuck::cast_slice(&float_data).to_vec(),
TextureFormat::Rgba32Float,
RenderAssetUsages::default(),
)
}
macro_rules! test_extract_and_transform_rgba_pixels {
($name:ident, $flip_x:expr, $flip_y:expr, $rect:expr, $expected:expr) => {
#[test]
fn $name() {
let image_data: &[u8] = &[
// Row 1: Red, Green, Blue
255, 0, 0, 255, // Red
0, 255, 0, 255, // Green
0, 0, 255, 255, // Blue
// Row 2: Yellow, Cyan, Magenta
255, 255, 0, 255, // Yellow
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 3: White, Gray, Black
255, 255, 255, 255, // White
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
];
// RGBA8 test
{
let image = create_image_rgba8(image_data);
let rect = $rect;
let result = extract_and_transform_rgba_pixels(&image, $flip_x, $flip_y, rect);
assert_eq!(result, Some($expected.to_vec()));
}
// RGBA32Float test
{
let image = create_image_rgba32float(image_data);
let rect = $rect;
let result = extract_and_transform_rgba_pixels(&image, $flip_x, $flip_y, rect);
assert_eq!(result, Some($expected.to_vec()));
}
}
};
}
test_extract_and_transform_rgba_pixels!(
no_flip_full_image,
false,
false,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 1: Red, Green, Blue
255, 0, 0, 255, // Red
0, 255, 0, 255, // Green
0, 0, 255, 255, // Blue
// Row 2: Yellow, Cyan, Magenta
255, 255, 0, 255, // Yellow
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 3: White, Gray, Black
255, 255, 255, 255, // White
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
]
);
test_extract_and_transform_rgba_pixels!(
flip_x_full_image,
true,
false,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 1 flipped: Blue, Green, Red
0, 0, 255, 255, // Blue
0, 255, 0, 255, // Green
255, 0, 0, 255, // Red
// Row 2 flipped: Magenta, Cyan, Yellow
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
255, 255, 0, 255, // Yellow
// Row 3 flipped: Black, Gray, White
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
255, 255, 255, 255, // White
]
);
test_extract_and_transform_rgba_pixels!(
flip_y_full_image,
false,
true,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 3: White, Gray, Black
255, 255, 255, 255, // White
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
// Row 2: Yellow, Cyan, Magenta
255, 255, 0, 255, // Yellow
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 1: Red, Green, Blue
255, 0, 0, 255, // Red
0, 255, 0, 255, // Green
0, 0, 255, 255, // Blue
]
);
test_extract_and_transform_rgba_pixels!(
flip_both_full_image,
true,
true,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 3 flipped: Black, Gray, White
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
255, 255, 255, 255, // White
// Row 2 flipped: Magenta, Cyan, Yellow
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
255, 255, 0, 255, // Yellow
// Row 1 flipped: Blue, Green, Red
0, 0, 255, 255, // Blue
0, 255, 0, 255, // Green
255, 0, 0, 255, // Red
]
);
test_extract_and_transform_rgba_pixels!(
no_flip_rect,
false,
false,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Only includes part of the original image (sub-rectangle)
// Row 2, columns 2-3: Cyan, Magenta
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 3, columns 2-3: Gray, Black
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
]
);
test_extract_and_transform_rgba_pixels!(
flip_x_rect,
true,
false,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Row 2 flipped: Magenta, Cyan
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
// Row 3 flipped: Black, Gray
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
]
);
test_extract_and_transform_rgba_pixels!(
flip_y_rect,
false,
true,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Row 3 first: Gray, Black
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
// Row 2 second: Cyan, Magenta
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
]
);
test_extract_and_transform_rgba_pixels!(
flip_both_rect,
true,
true,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Row 3 flipped: Black, Gray
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
// Row 2 flipped: Magenta, Cyan
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
]
);
#[test]
fn test_transform_hotspot() {
fn test(hotspot: (u16, u16), flip_x: bool, flip_y: bool, rect: Rect, expected: (u16, u16)) {
let transformed = transform_hotspot(hotspot, flip_x, flip_y, rect);
assert_eq!(transformed, expected);
// Round-trip test: Applying the same transformation again should
// reverse it.
let transformed = transform_hotspot(transformed, flip_x, flip_y, rect);
assert_eq!(transformed, hotspot);
}
let rect = Rect {
min: Vec2::ZERO,
max: Vec2::new(100.0, 200.0),
};
test((10, 20), false, false, rect, (10, 20)); // no flip
test((10, 20), true, false, rect, (89, 20)); // flip X
test((10, 20), false, true, rect, (10, 179)); // flip Y
test((10, 20), true, true, rect, (89, 179)); // flip both
test((0, 0), true, true, rect, (99, 199)); // flip both (bounds check)
}
}

221
vendor/bevy_winit/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,221 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
//! `bevy_winit` provides utilities to handle window creation and the eventloop through [`winit`]
//!
//! Most commonly, the [`WinitPlugin`] is used as part of
//! [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html).
//! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`].
//! See `winit_runner` for details.
extern crate alloc;
use bevy_derive::Deref;
use bevy_reflect::prelude::ReflectDefault;
use bevy_reflect::Reflect;
use bevy_window::{RawHandleWrapperHolder, WindowEvent};
use core::marker::PhantomData;
use winit::{event_loop::EventLoop, window::WindowId};
use bevy_a11y::AccessibilityRequested;
use bevy_app::{App, Last, Plugin};
use bevy_ecs::prelude::*;
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
use system::{changed_windows, check_keyboard_focus_lost, despawn_windows};
pub use system::{create_monitors, create_windows};
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub use winit::platform::web::CustomCursorExtWebSys;
pub use winit::{
event_loop::EventLoopProxy,
window::{CustomCursor as WinitCustomCursor, CustomCursorSource},
};
pub use winit_config::*;
pub use winit_windows::*;
use crate::{
accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers},
state::winit_runner,
winit_monitors::WinitMonitors,
};
pub mod accessibility;
mod converters;
pub mod cursor;
#[cfg(feature = "custom_cursor")]
mod custom_cursor;
mod state;
mod system;
mod winit_config;
mod winit_monitors;
mod winit_windows;
/// A [`Plugin`] that uses `winit` to create and manage windows, and receive window and input
/// events.
///
/// This plugin will add systems and resources that sync with the `winit` backend and also
/// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to
/// receive window and input events from the OS.
///
/// The `T` event type can be used to pass custom events to the `winit`'s loop, and handled as events
/// in systems.
///
/// When using eg. `MinimalPlugins` you can add this using `WinitPlugin::<WakeUp>::default()`, where
/// `WakeUp` is the default `Event` that bevy uses.
#[derive(Default)]
pub struct WinitPlugin<T: Event = WakeUp> {
/// Allows the window (and the event loop) to be created on any thread
/// instead of only the main thread.
///
/// See [`EventLoopBuilder::build`](winit::event_loop::EventLoopBuilder::build) for more information on this.
///
/// # Supported platforms
///
/// Only works on Linux (X11/Wayland) and Windows.
/// This field is ignored on other platforms.
pub run_on_any_thread: bool,
marker: PhantomData<T>,
}
impl<T: Event> Plugin for WinitPlugin<T> {
fn name(&self) -> &str {
"bevy_winit::WinitPlugin"
}
fn build(&self, app: &mut App) {
let mut event_loop_builder = EventLoop::<T>::with_user_event();
// linux check is needed because x11 might be enabled on other platforms.
#[cfg(all(target_os = "linux", feature = "x11"))]
{
use winit::platform::x11::EventLoopBuilderExtX11;
// This allows a Bevy app to be started and ran outside the main thread.
// A use case for this is to allow external applications to spawn a thread
// which runs a Bevy app without requiring the Bevy app to need to reside on
// the main thread, which can be problematic.
event_loop_builder.with_any_thread(self.run_on_any_thread);
}
// linux check is needed because wayland might be enabled on other platforms.
#[cfg(all(target_os = "linux", feature = "wayland"))]
{
use winit::platform::wayland::EventLoopBuilderExtWayland;
event_loop_builder.with_any_thread(self.run_on_any_thread);
}
#[cfg(target_os = "windows")]
{
use winit::platform::windows::EventLoopBuilderExtWindows;
event_loop_builder.with_any_thread(self.run_on_any_thread);
}
#[cfg(target_os = "android")]
{
use winit::platform::android::EventLoopBuilderExtAndroid;
let msg = "Bevy must be setup with the #[bevy_main] macro on Android";
event_loop_builder.with_android_app(bevy_window::ANDROID_APP.get().expect(msg).clone());
}
let event_loop = event_loop_builder
.build()
.expect("Failed to build event loop");
app.init_non_send_resource::<WinitWindows>()
.init_resource::<WinitMonitors>()
.init_resource::<WinitSettings>()
.insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle()))
.add_event::<RawWinitWindowEvent>()
.set_runner(|app| winit_runner(app, event_loop))
.add_systems(
Last,
(
// `exit_on_all_closed` only checks if windows exist but doesn't access data,
// so we don't need to care about its ordering relative to `changed_windows`
changed_windows.ambiguous_with(exit_on_all_closed),
despawn_windows,
check_keyboard_focus_lost,
)
.chain(),
);
app.add_plugins(AccessKitPlugin);
app.add_plugins(cursor::CursorPlugin);
}
}
/// The default event that can be used to wake the window loop
/// Wakes up the loop if in wait state
#[derive(Debug, Default, Clone, Copy, Event, Reflect)]
#[reflect(Debug, Default, Clone)]
pub struct WakeUp;
/// The original window event as produced by Winit. This is meant as an escape
/// hatch for power users that wish to add custom Winit integrations.
/// If you want to process events for your app or game, you should instead use
/// `bevy::window::WindowEvent`, or one of its sub-events.
///
/// When you receive this event it has already been handled by Bevy's main loop.
/// Sending these events will NOT cause them to be processed by Bevy.
#[derive(Debug, Clone, Event)]
pub struct RawWinitWindowEvent {
/// The window for which the event was fired.
pub window_id: WindowId,
/// The raw winit window event.
pub event: winit::event::WindowEvent,
}
/// A wrapper type around [`winit::event_loop::EventLoopProxy`] with the specific
/// [`winit::event::Event::UserEvent`] used in the [`WinitPlugin`].
///
/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
///
/// Use `Res<EventLoopProxy>` to receive this resource.
#[derive(Resource, Deref)]
pub struct EventLoopProxyWrapper<T: 'static>(EventLoopProxy<T>);
/// A wrapper around [`winit::event_loop::OwnedDisplayHandle`]
///
/// The `DisplayHandleWrapper` can be used to build integrations that rely on direct
/// access to the display handle
///
/// Use `Res<DisplayHandleWrapper>` to receive this resource.
#[derive(Resource, Deref)]
pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle);
trait AppSendEvent {
fn send(&mut self, event: impl Into<WindowEvent>);
}
impl AppSendEvent for Vec<WindowEvent> {
fn send(&mut self, event: impl Into<WindowEvent>) {
self.push(Into::<WindowEvent>::into(event));
}
}
/// The parameters of the [`create_windows`] system.
pub type CreateWindowParams<'w, 's, F = ()> = (
Commands<'w, 's>,
Query<
'w,
's,
(
Entity,
&'static mut Window,
Option<&'static RawHandleWrapperHolder>,
),
F,
>,
EventWriter<'w, WindowCreated>,
NonSendMut<'w, WinitWindows>,
NonSendMut<'w, AccessKitAdapters>,
ResMut<'w, WinitActionRequestHandlers>,
Res<'w, AccessibilityRequested>,
Res<'w, WinitMonitors>,
);
/// The parameters of the [`create_monitors`] system.
pub type CreateMonitorParams<'w, 's> = (Commands<'w, 's>, ResMut<'w, WinitMonitors>);

996
vendor/bevy_winit/src/state.rs vendored Normal file
View File

@@ -0,0 +1,996 @@
use approx::relative_eq;
use bevy_app::{App, AppExit, PluginsState};
#[cfg(feature = "custom_cursor")]
use bevy_asset::AssetId;
use bevy_ecs::{
change_detection::{DetectChanges, NonSendMut, Res},
entity::Entity,
event::{EventCursor, EventWriter},
prelude::*,
system::SystemState,
world::FromWorld,
};
#[cfg(feature = "custom_cursor")]
use bevy_image::{Image, TextureAtlasLayout};
use bevy_input::{
gestures::*,
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
};
#[cfg(any(not(target_arch = "wasm32"), feature = "custom_cursor"))]
use bevy_log::error;
use bevy_log::{trace, warn};
#[cfg(feature = "custom_cursor")]
use bevy_math::URect;
use bevy_math::{ivec2, DVec2, Vec2};
#[cfg(feature = "custom_cursor")]
use bevy_platform::collections::HashMap;
use bevy_platform::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::tick_global_task_pools_on_main_thread;
use core::marker::PhantomData;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::EventLoopExtWebSys;
use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
event,
event::{DeviceEvent, DeviceId, StartCause, WindowEvent},
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::WindowId,
};
use bevy_window::{
AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, RequestRedraw,
Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed,
WindowEvent as BevyWindowEvent, WindowFocused, WindowMoved, WindowOccluded, WindowResized,
WindowScaleFactorChanged, WindowThemeChanged,
};
#[cfg(target_os = "android")]
use bevy_window::{PrimaryWindow, RawHandleWrapper};
use crate::{
accessibility::AccessKitAdapters,
converters, create_windows,
system::{create_monitors, CachedWindow, WinitWindowPressedKeys},
AppSendEvent, CreateMonitorParams, CreateWindowParams, EventLoopProxyWrapper,
RawWinitWindowEvent, UpdateMode, 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 the app has already updated since the last redraw.
ran_update_since_last_redraw: 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,
/// Bevy window events to send
bevy_window_events: Vec<bevy_window::WindowEvent>,
/// Raw Winit window events to send
raw_winit_events: Vec<RawWinitWindowEvent>,
_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,
&'static mut WinitWindowPressedKeys,
),
>,
NonSendMut<'static, AccessKitAdapters>,
)>,
}
impl<T: Event> WinitAppRunnerState<T> {
fn new(mut app: App) -> Self {
app.add_event::<T>();
#[cfg(feature = "custom_cursor")]
app.init_resource::<CustomCursorCache>();
let event_writer_system_state: SystemState<(
EventWriter<WindowResized>,
EventWriter<WindowBackendScaleFactorChanged>,
EventWriter<WindowScaleFactorChanged>,
NonSend<WinitWindows>,
Query<(&mut Window, &mut CachedWindow, &mut WinitWindowPressedKeys)>,
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,
ran_update_since_last_redraw: false,
wait_elapsed: false,
// 3 seems to be enough, 5 is a safe margin
startup_forced_updates: 5,
bevy_window_events: Vec::new(),
raw_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()
}
}
#[cfg(feature = "custom_cursor")]
/// Identifiers for custom cursors used in caching.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum CustomCursorCacheKey {
/// A custom cursor with an image.
Image {
id: AssetId<Image>,
texture_atlas_layout_id: Option<AssetId<TextureAtlasLayout>>,
texture_atlas_index: Option<usize>,
flip_x: bool,
flip_y: bool,
rect: Option<URect>,
},
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
/// A custom cursor with a URL.
Url(String),
}
#[cfg(feature = "custom_cursor")]
/// Caches custom cursors. On many platforms, creating custom cursors is expensive, especially on
/// the web.
#[derive(Debug, Clone, Default, Resource)]
pub struct CustomCursorCache(pub HashMap<CustomCursorCacheKey, winit::window::CustomCursor>);
/// A source for a cursor. Consumed by the winit event loop.
#[derive(Debug)]
pub enum CursorSource {
#[cfg(feature = "custom_cursor")]
/// A custom cursor was identified to be cached, no reason to recreate it.
CustomCached(CustomCursorCacheKey),
#[cfg(feature = "custom_cursor")]
/// A custom cursor was not cached, so it needs to be created by the winit event loop.
Custom((CustomCursorCacheKey, winit::window::CustomCursorSource)),
/// A system cursor was requested.
System(winit::window::CursorIcon),
}
/// Component that indicates what cursor should be used for a window. Inserted
/// automatically after changing `CursorIcon` and consumed by the winit event
/// loop.
#[derive(Component, Debug)]
pub struct PendingCursor(pub Option<CursorSource>);
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 = 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, _, mut pressed_keys)) = windows.get_mut(window) else {
warn!("Window {window:?} is missing `Window` component, skipping event {event:?}");
return;
};
// Store a copy of the event to send to an EventWriter later.
self.raw_winit_events.push(RawWinitWindowEvent {
window_id,
event: event.clone(),
});
// 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
.bevy_window_events
.send(WindowCloseRequested { window }),
WindowEvent::KeyboardInput {
ref event,
// On some platforms, winit sends "synthetic" key press events when the window
// gains or loses focus. These are not implemented on every platform, so we ignore
// winit's synthetic key pressed and implement the same mechanism ourselves.
// (See the `WinitWindowPressedKeys` component)
is_synthetic: false,
..
} => {
let keyboard_input = converters::convert_keyboard_input(event, window);
if event.state.is_pressed() {
pressed_keys
.0
.insert(keyboard_input.key_code, keyboard_input.logical_key.clone());
} else {
pressed_keys.0.remove(&keyboard_input.key_code);
}
self.bevy_window_events.send(keyboard_input);
}
WindowEvent::CursorMoved { position, .. } => {
let physical_position = DVec2::new(position.x, position.y);
let last_position = win.physical_cursor_position();
let delta = last_position.map(|last_pos| {
(physical_position.as_vec2() - last_pos) / win.resolution.scale_factor()
});
win.set_physical_cursor_position(Some(physical_position));
let position = (physical_position / win.resolution.scale_factor() as f64).as_vec2();
self.bevy_window_events.send(CursorMoved {
window,
position,
delta,
});
}
WindowEvent::CursorEntered { .. } => {
self.bevy_window_events.send(CursorEntered { window });
}
WindowEvent::CursorLeft { .. } => {
win.set_physical_cursor_position(None);
self.bevy_window_events.send(CursorLeft { window });
}
WindowEvent::MouseInput { state, button, .. } => {
self.bevy_window_events.send(MouseButtonInput {
button: converters::convert_mouse_button(button),
state: converters::convert_element_state(state),
window,
});
}
WindowEvent::PinchGesture { delta, .. } => {
self.bevy_window_events.send(PinchGesture(delta as f32));
}
WindowEvent::RotationGesture { delta, .. } => {
self.bevy_window_events.send(RotationGesture(delta));
}
WindowEvent::DoubleTapGesture { .. } => {
self.bevy_window_events.send(DoubleTapGesture);
}
WindowEvent::PanGesture { delta, .. } => {
self.bevy_window_events.send(PanGesture(Vec2 {
x: delta.x,
y: delta.y,
}));
}
WindowEvent::MouseWheel { delta, .. } => match delta {
event::MouseScrollDelta::LineDelta(x, y) => {
self.bevy_window_events.send(MouseWheel {
unit: MouseScrollUnit::Line,
x,
y,
window,
});
}
event::MouseScrollDelta::PixelDelta(p) => {
self.bevy_window_events.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: p.x as f32,
y: p.y as f32,
window,
});
}
},
WindowEvent::Touch(touch) => {
let location = touch
.location
.to_logical(win.resolution.scale_factor() as f64);
self.bevy_window_events
.send(converters::convert_touch_input(touch, location, window));
}
WindowEvent::Focused(focused) => {
win.focused = focused;
self.bevy_window_events
.send(WindowFocused { window, focused });
}
WindowEvent::Occluded(occluded) => {
self.bevy_window_events
.send(WindowOccluded { window, occluded });
}
WindowEvent::DroppedFile(path_buf) => {
self.bevy_window_events
.send(FileDragAndDrop::DroppedFile { window, path_buf });
}
WindowEvent::HoveredFile(path_buf) => {
self.bevy_window_events
.send(FileDragAndDrop::HoveredFile { window, path_buf });
}
WindowEvent::HoveredFileCancelled => {
self.bevy_window_events
.send(FileDragAndDrop::HoveredFileCanceled { window });
}
WindowEvent::Moved(position) => {
let position = ivec2(position.x, position.y);
win.position.set(position);
self.bevy_window_events
.send(WindowMoved { window, position });
}
WindowEvent::Ime(event) => match event {
event::Ime::Preedit(value, cursor) => {
self.bevy_window_events.send(Ime::Preedit {
window,
value,
cursor,
});
}
event::Ime::Commit(value) => {
self.bevy_window_events.send(Ime::Commit { window, value });
}
event::Ime::Enabled => {
self.bevy_window_events.send(Ime::Enabled { window });
}
event::Ime::Disabled => {
self.bevy_window_events.send(Ime::Disabled { window });
}
},
WindowEvent::ThemeChanged(theme) => {
self.bevy_window_events.send(WindowThemeChanged {
window,
theme: converters::convert_winit_theme(theme),
});
}
WindowEvent::Destroyed => {
self.bevy_window_events.send(WindowDestroyed { window });
}
WindowEvent::RedrawRequested => {
self.ran_update_since_last_redraw = false;
// https://github.com/bevyengine/bevy/issues/17488
#[cfg(target_os = "windows")]
{
// Have the startup behavior run in about_to_wait, which prevents issues with
// invisible window creation. https://github.com/bevyengine/bevy/issues/18027
if self.startup_forced_updates == 0 {
self.redraw_requested(_event_loop);
}
}
}
_ => {}
}
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.bevy_window_events.send(MouseMotion { delta });
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
let mut create_monitor = SystemState::<CreateMonitorParams>::from_world(self.world_mut());
// create 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_monitors(event_loop, create_monitor.get_mut(self.world_mut()));
create_monitor.apply(self.world_mut());
create_windows(event_loop, create_window.get_mut(self.world_mut()));
create_window.apply(self.world_mut());
// TODO: This is a workaround for https://github.com/bevyengine/bevy/issues/17488
// while preserving the iOS fix in https://github.com/bevyengine/bevy/pull/11245
// The monitor sync logic likely belongs in monitor event handlers and not here.
#[cfg(not(target_os = "windows"))]
self.redraw_requested(event_loop);
// Have the startup behavior run in about_to_wait, which prevents issues with
// invisible window creation. https://github.com/bevyengine/bevy/issues/18027
#[cfg(target_os = "windows")]
{
let winit_windows = self.world().non_send_resource::<WinitWindows>();
let headless = winit_windows.windows.is_empty();
let exiting = self.app_exit.is_some();
let reactive = matches!(self.update_mode, UpdateMode::Reactive { .. });
let all_invisible = winit_windows
.windows
.iter()
.all(|(_, w)| !w.is_visible().unwrap_or(false));
if !exiting
&& (self.startup_forced_updates > 0 || headless || all_invisible || reactive)
{
self.redraw_requested(event_loop);
}
}
}
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 redraw_requested(&mut self, event_loop: &ActiveEventLoop) {
let mut redraw_event_reader = EventCursor::<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;
self.ran_update_since_last_redraw = false;
#[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()).unwrap();
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.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,
monitors,
) = create_window.get_mut(self.world_mut());
let winit_window = winit_windows.create_window(
event_loop,
entity,
&window,
&mut adapters,
&mut handlers,
&accessibility_requested,
&monitors,
);
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.bevy_window_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 {
let (_, windows) = focused_windows_state.get(self.world());
// If no windows exist, this will evaluate to `true`.
let all_invisible = windows.iter().all(|w| !w.1.visible);
// Not redrawing, but the timeout elapsed.
//
// Additional condition for Windows OS.
// If no windows are visible, redraw calls will never succeed, which results in no app update calls being performed.
// This is a temporary solution, full solution is mentioned here: https://github.com/bevyengine/bevy/issues/1343#issuecomment-770091684
if !self.ran_update_since_last_redraw || all_invisible {
self.run_app_update();
#[cfg(feature = "custom_cursor")]
self.update_cursors(event_loop);
#[cfg(not(feature = "custom_cursor"))]
self.update_cursors();
self.ran_update_since_last_redraw = true;
} else {
self.redraw_requested = true;
}
// 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 should_update(&self, update_mode: UpdateMode) -> bool {
let handle_event = match update_mode {
UpdateMode::Continuous => {
self.wait_elapsed
|| self.user_event_received
|| self.window_event_received
|| self.device_event_received
}
UpdateMode::Reactive {
react_to_device_events,
react_to_user_events,
react_to_window_events,
..
} => {
self.wait_elapsed
|| (react_to_device_events && self.device_event_received)
|| (react_to_user_events && self.user_event_received)
|| (react_to_window_events && self.window_event_received)
}
};
handle_event && self.lifecycle.is_active()
}
fn run_app_update(&mut self) {
self.reset_on_update();
self.forward_bevy_events();
if self.app.plugins_state() == PluginsState::Cleaned {
self.app.update();
}
}
fn forward_bevy_events(&mut self) {
let raw_winit_events = self.raw_winit_events.drain(..).collect::<Vec<_>>();
let buffered_events = self.bevy_window_events.drain(..).collect::<Vec<_>>();
let world = self.world_mut();
if !raw_winit_events.is_empty() {
world
.resource_mut::<Events<RawWinitWindowEvent>>()
.send_batch(raw_winit_events);
}
for winit_event in buffered_events.iter() {
match winit_event.clone() {
BevyWindowEvent::AppLifecycle(e) => {
world.send_event(e);
}
BevyWindowEvent::CursorEntered(e) => {
world.send_event(e);
}
BevyWindowEvent::CursorLeft(e) => {
world.send_event(e);
}
BevyWindowEvent::CursorMoved(e) => {
world.send_event(e);
}
BevyWindowEvent::FileDragAndDrop(e) => {
world.send_event(e);
}
BevyWindowEvent::Ime(e) => {
world.send_event(e);
}
BevyWindowEvent::RequestRedraw(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowBackendScaleFactorChanged(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowCloseRequested(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowCreated(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowDestroyed(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowFocused(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowMoved(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowOccluded(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowResized(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowScaleFactorChanged(e) => {
world.send_event(e);
}
BevyWindowEvent::WindowThemeChanged(e) => {
world.send_event(e);
}
BevyWindowEvent::MouseButtonInput(e) => {
world.send_event(e);
}
BevyWindowEvent::MouseMotion(e) => {
world.send_event(e);
}
BevyWindowEvent::MouseWheel(e) => {
world.send_event(e);
}
BevyWindowEvent::PinchGesture(e) => {
world.send_event(e);
}
BevyWindowEvent::RotationGesture(e) => {
world.send_event(e);
}
BevyWindowEvent::DoubleTapGesture(e) => {
world.send_event(e);
}
BevyWindowEvent::PanGesture(e) => {
world.send_event(e);
}
BevyWindowEvent::TouchInput(e) => {
world.send_event(e);
}
BevyWindowEvent::KeyboardInput(e) => {
world.send_event(e);
}
BevyWindowEvent::KeyboardFocusLost(e) => {
world.send_event(e);
}
}
}
if !buffered_events.is_empty() {
world
.resource_mut::<Events<BevyWindowEvent>>()
.send_batch(buffered_events);
}
}
fn update_cursors(&mut self, #[cfg(feature = "custom_cursor")] event_loop: &ActiveEventLoop) {
#[cfg(feature = "custom_cursor")]
let mut windows_state: SystemState<(
NonSendMut<WinitWindows>,
ResMut<CustomCursorCache>,
Query<(Entity, &mut PendingCursor), Changed<PendingCursor>>,
)> = SystemState::new(self.world_mut());
#[cfg(feature = "custom_cursor")]
let (winit_windows, mut cursor_cache, mut windows) =
windows_state.get_mut(self.world_mut());
#[cfg(not(feature = "custom_cursor"))]
let mut windows_state: SystemState<(
NonSendMut<WinitWindows>,
Query<(Entity, &mut PendingCursor), Changed<PendingCursor>>,
)> = SystemState::new(self.world_mut());
#[cfg(not(feature = "custom_cursor"))]
let (winit_windows, mut windows) = windows_state.get_mut(self.world_mut());
for (entity, mut pending_cursor) in windows.iter_mut() {
let Some(winit_window) = winit_windows.get_window(entity) else {
continue;
};
let Some(pending_cursor) = pending_cursor.0.take() else {
continue;
};
let final_cursor: winit::window::Cursor = match pending_cursor {
#[cfg(feature = "custom_cursor")]
CursorSource::CustomCached(cache_key) => {
let Some(cached_cursor) = cursor_cache.0.get(&cache_key) else {
error!("Cursor should have been cached, but was not found");
continue;
};
cached_cursor.clone().into()
}
#[cfg(feature = "custom_cursor")]
CursorSource::Custom((cache_key, cursor)) => {
let custom_cursor = event_loop.create_custom_cursor(cursor);
cursor_cache.0.insert(cache_key, custom_cursor.clone());
custom_cursor.into()
}
CursorSource::System(system_cursor) => system_cursor.into(),
};
winit_window.set_cursor(final_cursor);
}
}
}
/// The default [`App::runner`] for the [`WinitPlugin`](crate::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, event_loop: EventLoop<T>) -> AppExit {
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
}
app.world_mut()
.insert_resource(EventLoopProxyWrapper(event_loop.create_proxy()));
let runner_state = WinitAppRunnerState::new(app);
trace!("starting winit event loop");
// The winit docs mention using `spawn` instead of `run` on Wasm.
// https://docs.rs/winit/latest/winit/platform/web/trait.EventLoopExtWebSys.html#tymethod.spawn_app
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
event_loop.spawn_app(runner_state);
AppExit::Success
} else {
let mut runner_state = runner_state;
if let Err(err) = event_loop.run_app(&mut runner_state) {
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 an 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.write(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.write(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.write(WindowScaleFactorChanged {
window: window_entity,
scale_factor,
});
}
}

579
vendor/bevy_winit/src/system.rs vendored Normal file
View File

@@ -0,0 +1,579 @@
use std::collections::HashMap;
use bevy_ecs::{
entity::Entity,
event::EventWriter,
prelude::{Changed, Component},
query::QueryFilter,
removal_detection::RemovedComponents,
system::{Local, NonSendMut, Query, SystemParamItem},
};
use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput};
use bevy_window::{
ClosingWindow, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, WindowClosed,
WindowClosing, WindowCreated, WindowEvent, WindowFocused, WindowMode, WindowResized,
WindowWrapper,
};
use tracing::{error, info, warn};
use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
event_loop::ActiveEventLoop,
};
use bevy_app::AppExit;
use bevy_ecs::{prelude::EventReader, query::With, system::Res};
use bevy_math::{IVec2, UVec2};
#[cfg(target_os = "ios")]
use winit::platform::ios::WindowExtIOS;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowExtWebSys;
use crate::{
converters::{
convert_enabled_buttons, convert_resize_direction, convert_window_level,
convert_window_theme, convert_winit_theme,
},
get_selected_videomode, select_monitor,
state::react_to_resize,
winit_monitors::WinitMonitors,
CreateMonitorParams, CreateWindowParams, WinitWindows,
};
/// Creates new windows on the [`winit`] backend for each entity with a newly-added
/// [`Window`] component.
///
/// If any of these entities are missing required components, those will be added with their
/// default values.
pub fn create_windows<F: QueryFilter + 'static>(
event_loop: &ActiveEventLoop,
(
mut commands,
mut created_windows,
mut window_created_events,
mut winit_windows,
mut adapters,
mut handlers,
accessibility_requested,
monitors,
): SystemParamItem<CreateWindowParams<F>>,
) {
for (entity, mut window, handle_holder) in &mut created_windows {
if winit_windows.get_window(entity).is_some() {
continue;
}
info!("Creating new window {} ({})", window.title.as_str(), entity);
let winit_window = winit_windows.create_window(
event_loop,
entity,
&window,
&mut adapters,
&mut handlers,
&accessibility_requested,
&monitors,
);
if let Some(theme) = winit_window.theme() {
window.window_theme = Some(convert_winit_theme(theme));
}
window
.resolution
.set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32);
commands.entity(entity).insert((
CachedWindow {
window: window.clone(),
},
WinitWindowPressedKeys::default(),
));
if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) {
commands.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")]
{
if window.fit_canvas_to_parent {
let canvas = winit_window
.canvas()
.expect("window.canvas() can only be called in main thread.");
let style = canvas.style();
style.set_property("width", "100%").unwrap();
style.set_property("height", "100%").unwrap();
}
}
#[cfg(target_os = "ios")]
{
winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture);
winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture);
winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture);
if let Some((min, max)) = window.recognize_pan_gesture {
winit_window.recognize_pan_gesture(true, min, max);
} else {
winit_window.recognize_pan_gesture(false, 0, 0);
}
}
window_created_events.write(WindowCreated { window: entity });
}
}
/// Check whether keyboard focus was lost. This is different from window
/// focus in that swapping between Bevy windows keeps window focus.
pub(crate) fn check_keyboard_focus_lost(
mut focus_events: EventReader<WindowFocused>,
mut keyboard_focus: EventWriter<KeyboardFocusLost>,
mut keyboard_input: EventWriter<KeyboardInput>,
mut window_events: EventWriter<WindowEvent>,
mut q_windows: Query<&mut WinitWindowPressedKeys>,
) {
let mut focus_lost = vec![];
let mut focus_gained = false;
for e in focus_events.read() {
if e.focused {
focus_gained = true;
} else {
focus_lost.push(e.window);
}
}
if !focus_gained {
if !focus_lost.is_empty() {
window_events.write(WindowEvent::KeyboardFocusLost(KeyboardFocusLost));
keyboard_focus.write(KeyboardFocusLost);
}
for window in focus_lost {
let Ok(mut pressed_keys) = q_windows.get_mut(window) else {
continue;
};
for (key_code, logical_key) in pressed_keys.0.drain() {
let event = KeyboardInput {
key_code,
logical_key,
state: bevy_input::ButtonState::Released,
repeat: false,
window,
text: None,
};
window_events.write(WindowEvent::KeyboardInput(event.clone()));
keyboard_input.write(event);
}
}
}
}
/// Synchronize available monitors as reported by [`winit`] with [`Monitor`] entities in the world.
pub fn create_monitors(
event_loop: &ActiveEventLoop,
(mut commands, mut monitors): SystemParamItem<CreateMonitorParams>,
) {
let primary_monitor = event_loop.primary_monitor();
let mut seen_monitors = vec![false; monitors.monitors.len()];
'outer: for monitor in event_loop.available_monitors() {
for (idx, (m, _)) in monitors.monitors.iter().enumerate() {
if &monitor == m {
seen_monitors[idx] = true;
continue 'outer;
}
}
let size = monitor.size();
let position = monitor.position();
let entity = commands
.spawn(Monitor {
name: monitor.name(),
physical_height: size.height,
physical_width: size.width,
physical_position: IVec2::new(position.x, position.y),
refresh_rate_millihertz: monitor.refresh_rate_millihertz(),
scale_factor: monitor.scale_factor(),
video_modes: monitor
.video_modes()
.map(|v| {
let size = v.size();
VideoMode {
physical_size: UVec2::new(size.width, size.height),
bit_depth: v.bit_depth(),
refresh_rate_millihertz: v.refresh_rate_millihertz(),
}
})
.collect(),
})
.id();
if primary_monitor.as_ref() == Some(&monitor) {
commands.entity(entity).insert(PrimaryMonitor);
}
seen_monitors.push(true);
monitors.monitors.push((monitor, entity));
}
let mut idx = 0;
monitors.monitors.retain(|(_m, entity)| {
if seen_monitors[idx] {
idx += 1;
true
} else {
info!("Monitor removed {}", entity);
commands.entity(*entity).despawn();
idx += 1;
false
}
});
}
pub(crate) fn despawn_windows(
closing: Query<Entity, With<ClosingWindow>>,
mut closed: RemovedComponents<Window>,
window_entities: Query<Entity, With<Window>>,
mut closing_events: EventWriter<WindowClosing>,
mut closed_events: EventWriter<WindowClosed>,
mut winit_windows: NonSendMut<WinitWindows>,
mut windows_to_drop: Local<Vec<WindowWrapper<winit::window::Window>>>,
mut exit_events: EventReader<AppExit>,
) {
// Drop all the windows that are waiting to be closed
windows_to_drop.clear();
for window in closing.iter() {
closing_events.write(WindowClosing { window });
}
for window in closed.read() {
info!("Closing window {}", window);
// Guard to verify that the window is in fact actually gone,
// rather than having the component added
// and removed in the same frame.
if !window_entities.contains(window) {
if let Some(window) = winit_windows.remove_window(window) {
// Keeping WindowWrapper that are dropped for one frame
// Otherwise the last `Arc` of the window could be in the rendering thread, and dropped there
// This would hang on macOS
// Keeping the wrapper and dropping it next frame in this system ensure its dropped in the main thread
windows_to_drop.push(window);
}
closed_events.write(WindowClosed { window });
}
}
// On macOS, when exiting, we need to tell the rendering thread the windows are about to
// close to ensure that they are dropped on the main thread. Otherwise, the app will hang.
if !exit_events.is_empty() {
exit_events.clear();
for window in window_entities.iter() {
closing_events.write(WindowClosing { window });
}
}
}
/// The cached state of the window so we can check which properties were changed from within the app.
#[derive(Debug, Clone, Component)]
pub struct CachedWindow {
pub window: Window,
}
/// Propagates changes from [`Window`] entities to the [`winit`] backend.
///
/// # Notes
///
/// - [`Window::present_mode`] and [`Window::composite_alpha_mode`] changes are handled by the `bevy_render` crate.
/// - [`Window::transparent`] cannot be changed after the window is created.
/// - [`Window::canvas`] cannot be changed after the window is created.
/// - [`Window::focused`] cannot be manually changed to `false` after the window is created.
pub(crate) fn changed_windows(
mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed<Window>>,
winit_windows: NonSendMut<WinitWindows>,
monitors: Res<WinitMonitors>,
mut window_resized: EventWriter<WindowResized>,
) {
for (entity, mut window, mut cache) in &mut changed_windows {
let Some(winit_window) = winit_windows.get_window(entity) else {
continue;
};
if window.title != cache.window.title {
winit_window.set_title(window.title.as_str());
}
if window.mode != cache.window.mode {
let new_mode = match window.mode {
WindowMode::BorderlessFullscreen(monitor_selection) => {
Some(Some(winit::window::Fullscreen::Borderless(select_monitor(
&monitors,
winit_window.primary_monitor(),
winit_window.current_monitor(),
&monitor_selection,
))))
}
WindowMode::Fullscreen(monitor_selection, video_mode_selection) => {
let monitor = &select_monitor(
&monitors,
winit_window.primary_monitor(),
winit_window.current_monitor(),
&monitor_selection,
)
.unwrap_or_else(|| {
panic!("Could not find monitor for {:?}", monitor_selection)
});
if let Some(video_mode) = get_selected_videomode(monitor, &video_mode_selection)
{
Some(Some(winit::window::Fullscreen::Exclusive(video_mode)))
} else {
warn!(
"Could not find valid fullscreen video mode for {:?} {:?}",
monitor_selection, video_mode_selection
);
None
}
}
WindowMode::Windowed => Some(None),
};
if let Some(new_mode) = new_mode {
if winit_window.fullscreen() != new_mode {
winit_window.set_fullscreen(new_mode);
}
}
}
if window.resolution != cache.window.resolution {
let mut physical_size = PhysicalSize::new(
window.resolution.physical_width(),
window.resolution.physical_height(),
);
let cached_physical_size = PhysicalSize::new(
cache.window.physical_width(),
cache.window.physical_height(),
);
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);
}
}
}
if window.physical_cursor_position() != cache.window.physical_cursor_position() {
if let Some(physical_position) = window.physical_cursor_position() {
let position = PhysicalPosition::new(physical_position.x, physical_position.y);
if let Err(err) = winit_window.set_cursor_position(position) {
error!("could not set cursor position: {}", err);
}
}
}
if window.cursor_options.grab_mode != cache.window.cursor_options.grab_mode
&& crate::winit_windows::attempt_grab(winit_window, window.cursor_options.grab_mode)
.is_err()
{
window.cursor_options.grab_mode = cache.window.cursor_options.grab_mode;
}
if window.cursor_options.visible != cache.window.cursor_options.visible {
winit_window.set_cursor_visible(window.cursor_options.visible);
}
if window.cursor_options.hit_test != cache.window.cursor_options.hit_test {
if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) {
window.cursor_options.hit_test = cache.window.cursor_options.hit_test;
warn!(
"Could not set cursor hit test for window {}: {}",
window.title, err
);
}
}
if window.decorations != cache.window.decorations
&& window.decorations != winit_window.is_decorated()
{
winit_window.set_decorations(window.decorations);
}
if window.resizable != cache.window.resizable
&& window.resizable != winit_window.is_resizable()
{
winit_window.set_resizable(window.resizable);
}
if window.enabled_buttons != cache.window.enabled_buttons {
winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons));
}
if window.resize_constraints != cache.window.resize_constraints {
let constraints = window.resize_constraints.check_constraints();
let min_inner_size = LogicalSize {
width: constraints.min_width,
height: constraints.min_height,
};
let max_inner_size = LogicalSize {
width: constraints.max_width,
height: constraints.max_height,
};
winit_window.set_min_inner_size(Some(min_inner_size));
if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
winit_window.set_max_inner_size(Some(max_inner_size));
}
}
if window.position != cache.window.position {
if let Some(position) = crate::winit_window_position(
&window.position,
&window.resolution,
&monitors,
winit_window.primary_monitor(),
winit_window.current_monitor(),
) {
let should_set = match winit_window.outer_position() {
Ok(current_position) => current_position != position,
_ => true,
};
if should_set {
winit_window.set_outer_position(position);
}
}
}
if let Some(maximized) = window.internal.take_maximize_request() {
winit_window.set_maximized(maximized);
}
if let Some(minimized) = window.internal.take_minimize_request() {
winit_window.set_minimized(minimized);
}
if window.internal.take_move_request() {
if let Err(e) = winit_window.drag_window() {
warn!("Winit returned an error while attempting to drag the window: {e}");
}
}
if let Some(resize_direction) = window.internal.take_resize_request() {
if let Err(e) =
winit_window.drag_resize_window(convert_resize_direction(resize_direction))
{
warn!("Winit returned an error while attempting to drag resize the window: {e}");
}
}
if window.focused != cache.window.focused && window.focused {
winit_window.focus_window();
}
if window.window_level != cache.window.window_level {
winit_window.set_window_level(convert_window_level(window.window_level));
}
// Currently unsupported changes
if window.transparent != cache.window.transparent {
window.transparent = cache.window.transparent;
warn!("Winit does not currently support updating transparency after window creation.");
}
#[cfg(target_arch = "wasm32")]
if window.canvas != cache.window.canvas {
window.canvas.clone_from(&cache.window.canvas);
warn!(
"Bevy currently doesn't support modifying the window canvas after initialization."
);
}
if window.ime_enabled != cache.window.ime_enabled {
winit_window.set_ime_allowed(window.ime_enabled);
}
if window.ime_position != cache.window.ime_position {
winit_window.set_ime_cursor_area(
LogicalPosition::new(window.ime_position.x, window.ime_position.y),
PhysicalSize::new(10, 10),
);
}
if window.window_theme != cache.window.window_theme {
winit_window.set_theme(window.window_theme.map(convert_window_theme));
}
if window.visible != cache.window.visible {
winit_window.set_visible(window.visible);
}
#[cfg(target_os = "ios")]
{
if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture {
winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture);
}
if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture {
winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture);
}
if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture {
winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture);
}
if window.recognize_pan_gesture != cache.window.recognize_pan_gesture {
match (
window.recognize_pan_gesture,
cache.window.recognize_pan_gesture,
) {
(Some(_), Some(_)) => {
warn!("Bevy currently doesn't support modifying PanGesture number of fingers recognition. Please disable it before re-enabling it with the new number of fingers");
}
(Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max),
_ => winit_window.recognize_pan_gesture(false, 0, 0),
}
}
if window.prefers_home_indicator_hidden != cache.window.prefers_home_indicator_hidden {
winit_window
.set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden);
}
if window.prefers_status_bar_hidden != cache.window.prefers_status_bar_hidden {
winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden);
}
}
cache.window = window.clone();
}
}
/// This keeps track of which keys are pressed on each window.
/// When a window is unfocused, this is used to send key release events for all the currently held keys.
#[derive(Default, Component)]
pub struct WinitWindowPressedKeys(pub(crate) HashMap<KeyCode, Key>);

125
vendor/bevy_winit/src/winit_config.rs vendored Normal file
View File

@@ -0,0 +1,125 @@
use bevy_ecs::resource::Resource;
use core::time::Duration;
/// Settings for the [`WinitPlugin`](super::WinitPlugin).
#[derive(Debug, Resource, Clone)]
pub struct WinitSettings {
/// Determines how frequently the application can update when it has focus.
pub focused_mode: UpdateMode,
/// Determines how frequently the application can update when it's out of focus.
pub unfocused_mode: UpdateMode,
}
impl WinitSettings {
/// Default settings for games.
///
/// [`Continuous`](UpdateMode::Continuous) if windows have focus,
/// [`reactive_low_power`](UpdateMode::reactive_low_power) otherwise.
pub fn game() -> Self {
WinitSettings {
focused_mode: UpdateMode::Continuous,
unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs_f64(1.0 / 60.0)), /* 60Hz, */
}
}
/// Default settings for desktop applications.
///
/// [`Reactive`](UpdateMode::Reactive) if windows have focus,
/// [`reactive_low_power`](UpdateMode::reactive_low_power) otherwise.
///
/// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy.
pub fn desktop_app() -> Self {
WinitSettings {
focused_mode: UpdateMode::reactive(Duration::from_secs(5)),
unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(60)),
}
}
/// Default settings for mobile.
///
/// [`Reactive`](UpdateMode::Reactive) if windows have focus,
/// [`reactive_low_power`](UpdateMode::reactive_low_power) otherwise.
///
/// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy.
pub fn mobile() -> Self {
WinitSettings {
focused_mode: UpdateMode::reactive(Duration::from_secs_f32(1.0 / 60.0)),
unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(1)),
}
}
/// Returns the current [`UpdateMode`].
///
/// **Note:** The output depends on whether the window has focus or not.
pub fn update_mode(&self, focused: bool) -> UpdateMode {
match focused {
true => self.focused_mode,
false => self.unfocused_mode,
}
}
}
impl Default for WinitSettings {
fn default() -> Self {
WinitSettings::game()
}
}
/// Determines how frequently an [`App`](bevy_app::App) should update.
///
/// **Note:** This setting is independent of VSync. VSync is controlled by a window's
/// [`PresentMode`](bevy_window::PresentMode) setting. If an app can update faster than the refresh
/// rate, but VSync is enabled, the update rate will be indirectly limited by the renderer.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UpdateMode {
/// The [`App`](bevy_app::App) will update over and over, as fast as it possibly can, until an
/// [`AppExit`](bevy_app::AppExit) event appears.
Continuous,
/// The [`App`](bevy_app::App) will update in response to the following, until an
/// [`AppExit`](bevy_app::AppExit) event appears:
/// - `wait` time has elapsed since the previous update
/// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw)
/// - new [window](`winit::event::WindowEvent`), [raw input](`winit::event::DeviceEvent`), or custom
/// events have appeared
/// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy)
Reactive {
/// The approximate time from the start of one update to the next.
///
/// **Note:** This has no upper limit.
/// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].
wait: Duration,
/// Reacts to device events, that will wake up the loop if it's in a wait state
react_to_device_events: bool,
/// Reacts to user events, that will wake up the loop if it's in a wait state
react_to_user_events: bool,
/// Reacts to window events, that will wake up the loop if it's in a wait state
react_to_window_events: bool,
},
}
impl UpdateMode {
/// Reactive mode, will update the app for any kind of event
pub fn reactive(wait: Duration) -> Self {
Self::Reactive {
wait,
react_to_device_events: true,
react_to_user_events: true,
react_to_window_events: true,
}
}
/// Low power mode
///
/// Unlike [`Reactive`](`UpdateMode::reactive()`), this will ignore events that
/// don't come from interacting with a window, like [`MouseMotion`](winit::event::DeviceEvent::MouseMotion).
/// Use this if, for example, you only want your app to update when the mouse cursor is
/// moving over a window, not just moving in general. This can greatly reduce power consumption.
pub fn reactive_low_power(wait: Duration) -> Self {
Self::Reactive {
wait,
react_to_device_events: false,
react_to_user_events: true,
react_to_window_events: true,
}
}
}

34
vendor/bevy_winit/src/winit_monitors.rs vendored Normal file
View File

@@ -0,0 +1,34 @@
use winit::monitor::MonitorHandle;
use bevy_ecs::{entity::Entity, resource::Resource};
/// Stores [`winit`] monitors and their corresponding entities
///
/// # Known Issues
///
/// On some platforms, physically disconnecting a monitor might result in a
/// panic in [`winit`]'s loop. This will lead to a crash in the bevy app. See
/// [13669] for investigations and discussions.
///
/// [13669]: https://github.com/bevyengine/bevy/pull/13669
#[derive(Resource, Debug, Default)]
pub struct WinitMonitors {
/// Stores [`winit`] monitors and their corresponding entities
// We can't use a `BtreeMap` here because clippy complains about using `MonitorHandle` as a key
// on some platforms. Using a `Vec` is fine because we don't expect to have a large number of
// monitors and avoids having to audit the code for `MonitorHandle` equality.
pub(crate) monitors: Vec<(MonitorHandle, Entity)>,
}
impl WinitMonitors {
pub fn nth(&self, n: usize) -> Option<MonitorHandle> {
self.monitors.get(n).map(|(monitor, _)| monitor.clone())
}
pub fn find_entity(&self, entity: Entity) -> Option<MonitorHandle> {
self.monitors
.iter()
.find(|(_, e)| *e == entity)
.map(|(monitor, _)| monitor.clone())
}
}

516
vendor/bevy_winit/src/winit_windows.rs vendored Normal file
View File

@@ -0,0 +1,516 @@
use bevy_a11y::AccessibilityRequested;
use bevy_ecs::entity::Entity;
use bevy_ecs::entity::EntityHashMap;
use bevy_platform::collections::HashMap;
use bevy_window::{
CursorGrabMode, MonitorSelection, VideoModeSelection, Window, WindowMode, WindowPosition,
WindowResolution, WindowWrapper,
};
use tracing::warn;
use winit::{
dpi::{LogicalSize, PhysicalPosition},
error::ExternalError,
event_loop::ActiveEventLoop,
monitor::{MonitorHandle, VideoModeHandle},
window::{CursorGrabMode as WinitCursorGrabMode, Fullscreen, Window as WinitWindow, WindowId},
};
use crate::{
accessibility::{
prepare_accessibility_for_window, AccessKitAdapters, WinitActionRequestHandlers,
},
converters::{convert_enabled_buttons, convert_window_level, convert_window_theme},
winit_monitors::WinitMonitors,
};
/// A resource mapping window entities to their `winit`-backend [`Window`](winit::window::Window)
/// states.
#[derive(Debug, Default)]
pub struct WinitWindows {
/// Stores [`winit`] windows by window identifier.
pub windows: HashMap<WindowId, WindowWrapper<WinitWindow>>,
/// Maps entities to `winit` window identifiers.
pub entity_to_winit: EntityHashMap<WindowId>,
/// Maps `winit` window identifiers to entities.
pub winit_to_entity: HashMap<WindowId, Entity>,
// Many `winit` window functions (e.g. `set_window_icon`) can only be called on the main thread.
// If they're called on other threads, the program might hang. This marker indicates that this
// type is not thread-safe and will be `!Send` and `!Sync`.
_not_send_sync: core::marker::PhantomData<*const ()>,
}
impl WinitWindows {
/// Creates a `winit` window and associates it with our entity.
pub fn create_window(
&mut self,
event_loop: &ActiveEventLoop,
entity: Entity,
window: &Window,
adapters: &mut AccessKitAdapters,
handlers: &mut WinitActionRequestHandlers,
accessibility_requested: &AccessibilityRequested,
monitors: &WinitMonitors,
) -> &WindowWrapper<WinitWindow> {
let mut winit_window_attributes = WinitWindow::default_attributes();
// Due to a UIA limitation, winit windows need to be invisible for the
// AccessKit adapter is initialized.
winit_window_attributes = winit_window_attributes.with_visible(false);
let maybe_selected_monitor = &match window.mode {
WindowMode::BorderlessFullscreen(monitor_selection)
| WindowMode::Fullscreen(monitor_selection, _) => select_monitor(
monitors,
event_loop.primary_monitor(),
None,
&monitor_selection,
),
WindowMode::Windowed => None,
};
winit_window_attributes = match window.mode {
WindowMode::BorderlessFullscreen(_) => winit_window_attributes
.with_fullscreen(Some(Fullscreen::Borderless(maybe_selected_monitor.clone()))),
WindowMode::Fullscreen(monitor_selection, video_mode_selection) => {
let select_monitor = &maybe_selected_monitor
.clone()
.expect("Unable to get monitor.");
if let Some(video_mode) =
get_selected_videomode(select_monitor, &video_mode_selection)
{
winit_window_attributes.with_fullscreen(Some(Fullscreen::Exclusive(video_mode)))
} else {
warn!(
"Could not find valid fullscreen video mode for {:?} {:?}",
monitor_selection, video_mode_selection
);
winit_window_attributes
}
}
WindowMode::Windowed => {
if let Some(position) = winit_window_position(
&window.position,
&window.resolution,
monitors,
event_loop.primary_monitor(),
None,
) {
winit_window_attributes = winit_window_attributes.with_position(position);
}
let logical_size = LogicalSize::new(window.width(), window.height());
if let Some(sf) = window.resolution.scale_factor_override() {
let inner_size = logical_size.to_physical::<f64>(sf.into());
winit_window_attributes.with_inner_size(inner_size)
} else {
winit_window_attributes.with_inner_size(logical_size)
}
}
};
// It's crucial to avoid setting the window's final visibility here;
// as explained above, the window must be invisible until the AccessKit
// adapter is created.
winit_window_attributes = winit_window_attributes
.with_window_level(convert_window_level(window.window_level))
.with_theme(window.window_theme.map(convert_window_theme))
.with_resizable(window.resizable)
.with_enabled_buttons(convert_enabled_buttons(window.enabled_buttons))
.with_decorations(window.decorations)
.with_transparent(window.transparent);
#[cfg(target_os = "windows")]
{
use winit::platform::windows::WindowAttributesExtWindows;
winit_window_attributes =
winit_window_attributes.with_skip_taskbar(window.skip_taskbar);
winit_window_attributes =
winit_window_attributes.with_clip_children(window.clip_children);
}
#[cfg(target_os = "macos")]
{
use winit::platform::macos::WindowAttributesExtMacOS;
winit_window_attributes = winit_window_attributes
.with_movable_by_window_background(window.movable_by_window_background)
.with_fullsize_content_view(window.fullsize_content_view)
.with_has_shadow(window.has_shadow)
.with_titlebar_hidden(!window.titlebar_shown)
.with_titlebar_transparent(window.titlebar_transparent)
.with_title_hidden(!window.titlebar_show_title)
.with_titlebar_buttons_hidden(!window.titlebar_show_buttons);
}
#[cfg(target_os = "ios")]
{
use winit::platform::ios::WindowAttributesExtIOS;
winit_window_attributes = winit_window_attributes
.with_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden);
winit_window_attributes = winit_window_attributes
.with_prefers_status_bar_hidden(window.prefers_status_bar_hidden);
}
let display_info = DisplayInfo {
window_physical_resolution: (
window.resolution.physical_width(),
window.resolution.physical_height(),
),
window_logical_resolution: (window.resolution.width(), window.resolution.height()),
monitor_name: maybe_selected_monitor
.as_ref()
.and_then(MonitorHandle::name),
scale_factor: maybe_selected_monitor
.as_ref()
.map(MonitorHandle::scale_factor),
refresh_rate_millihertz: maybe_selected_monitor
.as_ref()
.and_then(MonitorHandle::refresh_rate_millihertz),
};
bevy_log::debug!("{display_info}");
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "windows"
))]
if let Some(name) = &window.name {
#[cfg(all(
feature = "wayland",
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)
))]
{
winit_window_attributes =
winit::platform::wayland::WindowAttributesExtWayland::with_name(
winit_window_attributes,
name.clone(),
"",
);
}
#[cfg(all(
feature = "x11",
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)
))]
{
winit_window_attributes = winit::platform::x11::WindowAttributesExtX11::with_name(
winit_window_attributes,
name.clone(),
"",
);
}
#[cfg(target_os = "windows")]
{
winit_window_attributes =
winit::platform::windows::WindowAttributesExtWindows::with_class_name(
winit_window_attributes,
name.clone(),
);
}
}
let constraints = window.resize_constraints.check_constraints();
let min_inner_size = LogicalSize {
width: constraints.min_width,
height: constraints.min_height,
};
let max_inner_size = LogicalSize {
width: constraints.max_width,
height: constraints.max_height,
};
let winit_window_attributes =
if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
winit_window_attributes
.with_min_inner_size(min_inner_size)
.with_max_inner_size(max_inner_size)
} else {
winit_window_attributes.with_min_inner_size(min_inner_size)
};
#[expect(clippy::allow_attributes, reason = "`unused_mut` is not always linted")]
#[allow(
unused_mut,
reason = "This variable needs to be mutable if `cfg(target_arch = \"wasm32\")`"
)]
let mut winit_window_attributes = winit_window_attributes.with_title(window.title.as_str());
#[cfg(target_arch = "wasm32")]
{
use wasm_bindgen::JsCast;
use winit::platform::web::WindowAttributesExtWebSys;
if let Some(selector) = &window.canvas {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = document
.query_selector(selector)
.expect("Cannot query for canvas element.");
if let Some(canvas) = canvas {
let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
winit_window_attributes = winit_window_attributes.with_canvas(canvas);
} else {
panic!("Cannot find element: {}.", selector);
}
}
winit_window_attributes =
winit_window_attributes.with_prevent_default(window.prevent_default_event_handling);
winit_window_attributes = winit_window_attributes.with_append(true);
}
let winit_window = event_loop.create_window(winit_window_attributes).unwrap();
let name = window.title.clone();
prepare_accessibility_for_window(
event_loop,
&winit_window,
entity,
name,
accessibility_requested.clone(),
adapters,
handlers,
);
// Now that the AccessKit adapter is created, it's safe to show
// the window.
winit_window.set_visible(window.visible);
// Do not set the grab mode on window creation if it's none. It can fail on mobile.
if window.cursor_options.grab_mode != CursorGrabMode::None {
let _ = attempt_grab(&winit_window, window.cursor_options.grab_mode);
}
winit_window.set_cursor_visible(window.cursor_options.visible);
// Do not set the cursor hittest on window creation if it's false, as it will always fail on
// some platforms and log an unfixable warning.
if !window.cursor_options.hit_test {
if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) {
warn!(
"Could not set cursor hit test for window {}: {}",
window.title, err
);
}
}
self.entity_to_winit.insert(entity, winit_window.id());
self.winit_to_entity.insert(winit_window.id(), entity);
self.windows
.entry(winit_window.id())
.insert(WindowWrapper::new(winit_window))
.into_mut()
}
/// Get the winit window that is associated with our entity.
pub fn get_window(&self, entity: Entity) -> Option<&WindowWrapper<WinitWindow>> {
self.entity_to_winit
.get(&entity)
.and_then(|winit_id| self.windows.get(winit_id))
}
/// Get the entity associated with the winit window id.
///
/// This is mostly just an intermediary step between us and winit.
pub fn get_window_entity(&self, winit_id: WindowId) -> Option<Entity> {
self.winit_to_entity.get(&winit_id).cloned()
}
/// Remove a window from winit.
///
/// This should mostly just be called when the window is closing.
pub fn remove_window(&mut self, entity: Entity) -> Option<WindowWrapper<WinitWindow>> {
let winit_id = self.entity_to_winit.remove(&entity)?;
self.winit_to_entity.remove(&winit_id);
self.windows.remove(&winit_id)
}
}
/// Returns some [`winit::monitor::VideoModeHandle`] given a [`MonitorHandle`] and a
/// [`VideoModeSelection`] or None if no valid matching video mode was found.
pub fn get_selected_videomode(
monitor: &MonitorHandle,
selection: &VideoModeSelection,
) -> Option<VideoModeHandle> {
match selection {
VideoModeSelection::Current => get_current_videomode(monitor),
VideoModeSelection::Specific(specified) => monitor.video_modes().find(|mode| {
mode.size().width == specified.physical_size.x
&& mode.size().height == specified.physical_size.y
&& mode.refresh_rate_millihertz() == specified.refresh_rate_millihertz
&& mode.bit_depth() == specified.bit_depth
}),
}
}
/// Gets a monitor's current video-mode.
///
/// TODO: When Winit 0.31 releases this function can be removed and replaced with
/// `MonitorHandle::current_video_mode()`
fn get_current_videomode(monitor: &MonitorHandle) -> Option<VideoModeHandle> {
monitor
.video_modes()
.filter(|mode| {
mode.size() == monitor.size()
&& Some(mode.refresh_rate_millihertz()) == monitor.refresh_rate_millihertz()
})
.max_by_key(VideoModeHandle::bit_depth)
}
pub(crate) fn attempt_grab(
winit_window: &WinitWindow,
grab_mode: CursorGrabMode,
) -> Result<(), ExternalError> {
let grab_result = match grab_mode {
CursorGrabMode::None => winit_window.set_cursor_grab(WinitCursorGrabMode::None),
CursorGrabMode::Confined => winit_window
.set_cursor_grab(WinitCursorGrabMode::Confined)
.or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Locked)),
CursorGrabMode::Locked => winit_window
.set_cursor_grab(WinitCursorGrabMode::Locked)
.or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Confined)),
};
if let Err(err) = grab_result {
let err_desc = match grab_mode {
CursorGrabMode::Confined | CursorGrabMode::Locked => "grab",
CursorGrabMode::None => "ungrab",
};
tracing::error!("Unable to {} cursor: {}", err_desc, err);
Err(err)
} else {
Ok(())
}
}
/// Compute the physical window position for a given [`WindowPosition`].
// Ideally we could generify this across window backends, but we only really have winit atm
// so whatever.
pub fn winit_window_position(
position: &WindowPosition,
resolution: &WindowResolution,
monitors: &WinitMonitors,
primary_monitor: Option<MonitorHandle>,
current_monitor: Option<MonitorHandle>,
) -> Option<PhysicalPosition<i32>> {
match position {
WindowPosition::Automatic => {
// Window manager will handle position
None
}
WindowPosition::Centered(monitor_selection) => {
let maybe_monitor = select_monitor(
monitors,
primary_monitor,
current_monitor,
monitor_selection,
);
if let Some(monitor) = maybe_monitor {
let screen_size = monitor.size();
let scale_factor = match resolution.scale_factor_override() {
Some(scale_factor_override) => scale_factor_override as f64,
// We use the monitors scale factor here since `WindowResolution.scale_factor` is
// not yet populated when windows are created during plugin setup.
None => monitor.scale_factor(),
};
// Logical to physical window size
let (width, height): (u32, u32) =
LogicalSize::new(resolution.width(), resolution.height())
.to_physical::<u32>(scale_factor)
.into();
let position = PhysicalPosition {
x: screen_size.width.saturating_sub(width) as f64 / 2.
+ monitor.position().x as f64,
y: screen_size.height.saturating_sub(height) as f64 / 2.
+ monitor.position().y as f64,
};
Some(position.cast::<i32>())
} else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}");
None
}
}
WindowPosition::At(position) => {
Some(PhysicalPosition::new(position[0] as f64, position[1] as f64).cast::<i32>())
}
}
}
/// Selects a monitor based on the given [`MonitorSelection`].
pub fn select_monitor(
monitors: &WinitMonitors,
primary_monitor: Option<MonitorHandle>,
current_monitor: Option<MonitorHandle>,
monitor_selection: &MonitorSelection,
) -> Option<MonitorHandle> {
use bevy_window::MonitorSelection::*;
match monitor_selection {
Current => {
if current_monitor.is_none() {
warn!("Can't select current monitor on window creation or cannot find current monitor!");
}
current_monitor
}
Primary => primary_monitor,
Index(n) => monitors.nth(*n),
Entity(entity) => monitors.find_entity(*entity),
}
}
struct DisplayInfo {
window_physical_resolution: (u32, u32),
window_logical_resolution: (f32, f32),
monitor_name: Option<String>,
scale_factor: Option<f64>,
refresh_rate_millihertz: Option<u32>,
}
impl core::fmt::Display for DisplayInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Display information:")?;
write!(
f,
" Window physical resolution: {}x{}",
self.window_physical_resolution.0, self.window_physical_resolution.1
)?;
write!(
f,
" Window logical resolution: {}x{}",
self.window_logical_resolution.0, self.window_logical_resolution.1
)?;
write!(
f,
" Monitor name: {}",
self.monitor_name.as_deref().unwrap_or("")
)?;
write!(f, " Scale factor: {}", self.scale_factor.unwrap_or(0.))?;
let millihertz = self.refresh_rate_millihertz.unwrap_or(0);
let hertz = millihertz / 1000;
let extra_millihertz = millihertz % 1000;
write!(f, " Refresh rate (Hz): {}.{:03}", hertz, extra_millihertz)?;
Ok(())
}
}