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

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(())
}
}