bevy_winit/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![forbid(unsafe_code)]
3#![doc(
4 html_logo_url = "https://bevy.org/assets/icon.png",
5 html_favicon_url = "https://bevy.org/assets/icon.png"
6)]
7
8//! `bevy_winit` provides utilities to handle window creation and the eventloop through [`winit`]
9//!
10//! Most commonly, the [`WinitPlugin`] is used as part of
11//! [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html).
12//! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`].
13//! See `winit_runner` for details.
14
15extern crate alloc;
16
17use bevy_derive::Deref;
18use bevy_reflect::Reflect;
19use bevy_window::{RawHandleWrapperHolder, WindowEvent};
20use core::cell::RefCell;
21use winit::{event_loop::EventLoop, window::WindowId};
22
23use bevy_a11y::AccessibilityRequested;
24use bevy_app::{App, Last, Plugin};
25use bevy_ecs::prelude::*;
26use bevy_window::{exit_on_all_closed, CursorOptions, Window, WindowCreated};
27use system::{changed_cursor_options, changed_windows, check_keyboard_focus_lost, despawn_windows};
28pub use system::{create_monitors, create_windows};
29#[cfg(all(target_family = "wasm", target_os = "unknown"))]
30pub use winit::platform::web::CustomCursorExtWebSys;
31pub use winit::{
32 event_loop::EventLoopProxy,
33 window::{CustomCursor as WinitCustomCursor, CustomCursorSource},
34};
35pub use winit_config::*;
36pub use winit_monitors::*;
37pub use winit_windows::*;
38
39use crate::{
40 accessibility::{AccessKitPlugin, WinitActionRequestHandlers},
41 state::winit_runner,
42};
43
44pub mod accessibility;
45pub mod converters;
46mod cursor;
47mod state;
48mod system;
49mod winit_config;
50mod winit_monitors;
51mod winit_windows;
52
53thread_local! {
54 /// Temporary storage of WinitWindows data to replace usage of `!Send` resources. This will be replaced with proper
55 /// storage of `!Send` data after issue #17667 is complete.
56 pub static WINIT_WINDOWS: RefCell<WinitWindows> = const { RefCell::new(WinitWindows::new()) };
57}
58
59/// A [`Plugin`] that uses `winit` to create and manage windows, and receive window and input
60/// events.
61///
62/// This plugin will add systems and resources that sync with the `winit` backend and also
63/// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to
64/// receive window and input events from the OS.
65///
66/// The `M` message type can be used to pass custom messages to the `winit`'s loop, and handled as messages
67/// in systems.
68///
69/// When using eg. `MinimalPlugins` you can add this using `WinitPlugin::<WakeUp>::default()`, where
70/// `WakeUp` is the default event that bevy uses.
71#[derive(Default)]
72pub struct WinitPlugin {
73 /// Allows the window (and the event loop) to be created on any thread
74 /// instead of only the main thread.
75 ///
76 /// See [`EventLoopBuilder::build`](winit::event_loop::EventLoopBuilder::build) for more information on this.
77 ///
78 /// # Supported platforms
79 ///
80 /// Only works on Linux (X11/Wayland) and Windows.
81 /// This field is ignored on other platforms.
82 pub run_on_any_thread: bool,
83}
84
85impl Plugin for WinitPlugin {
86 fn name(&self) -> &str {
87 "bevy_winit::WinitPlugin"
88 }
89
90 fn build(&self, app: &mut App) {
91 let mut event_loop_builder = EventLoop::<WinitUserEvent>::with_user_event();
92
93 // linux check is needed because x11 might be enabled on other platforms.
94 #[cfg(all(target_os = "linux", feature = "x11"))]
95 {
96 use winit::platform::x11::EventLoopBuilderExtX11;
97
98 // This allows a Bevy app to be started and ran outside the main thread.
99 // A use case for this is to allow external applications to spawn a thread
100 // which runs a Bevy app without requiring the Bevy app to need to reside on
101 // the main thread, which can be problematic.
102 event_loop_builder.with_any_thread(self.run_on_any_thread);
103 }
104
105 // linux check is needed because wayland might be enabled on other platforms.
106 #[cfg(all(target_os = "linux", feature = "wayland"))]
107 {
108 use winit::platform::wayland::EventLoopBuilderExtWayland;
109 event_loop_builder.with_any_thread(self.run_on_any_thread);
110 }
111
112 #[cfg(target_os = "windows")]
113 {
114 use winit::platform::windows::EventLoopBuilderExtWindows;
115 event_loop_builder.with_any_thread(self.run_on_any_thread);
116 }
117
118 #[cfg(target_os = "android")]
119 {
120 use winit::platform::android::EventLoopBuilderExtAndroid;
121 let msg = "Bevy must be setup with the #[bevy_main] macro on Android";
122 event_loop_builder
123 .with_android_app(bevy_android::ANDROID_APP.get().expect(msg).clone());
124 }
125
126 let event_loop = event_loop_builder
127 .build()
128 .expect("Failed to build event loop");
129
130 app.init_resource::<WinitMonitors>()
131 .init_resource::<WinitSettings>()
132 .insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle()))
133 .insert_resource(EventLoopProxyWrapper(event_loop.create_proxy()))
134 .add_message::<RawWinitWindowEvent>()
135 .set_runner(|app| winit_runner(app, event_loop))
136 .add_systems(
137 Last,
138 (
139 // `exit_on_all_closed` only checks if windows exist but doesn't access data,
140 // so we don't need to care about its ordering relative to `changed_windows`
141 changed_windows.ambiguous_with(exit_on_all_closed),
142 changed_cursor_options,
143 despawn_windows,
144 check_keyboard_focus_lost,
145 )
146 .chain(),
147 );
148
149 app.add_plugins(AccessKitPlugin);
150 app.add_plugins(cursor::WinitCursorPlugin);
151
152 app.add_observer(
153 |_window: On<Add, Window>, event_loop_proxy: Res<EventLoopProxyWrapper>| -> Result {
154 event_loop_proxy.send_event(WinitUserEvent::WindowAdded)?;
155
156 Ok(())
157 },
158 );
159 }
160}
161
162/// Events that can be sent to perform actions inside the winit event loop.
163///
164/// Sent via the [`EventLoopProxyWrapper`] resource.
165///
166/// # Example
167///
168/// ```
169/// # use bevy_ecs::prelude::*;
170/// # use bevy_winit::{EventLoopProxyWrapper, WinitUserEvent};
171/// fn wakeup_system(event_loop_proxy: Res<EventLoopProxyWrapper>) -> Result {
172/// event_loop_proxy.send_event(WinitUserEvent::WakeUp)?;
173///
174/// Ok(())
175/// }
176/// ```
177#[derive(Debug, Clone, Copy, Reflect)]
178#[reflect(Debug, Clone)]
179pub enum WinitUserEvent {
180 /// Dummy event that just wakes up the winit event loop
181 WakeUp,
182 /// Tell winit that a window needs to be created
183 WindowAdded,
184}
185
186/// The original window event as produced by Winit. This is meant as an escape
187/// hatch for power users that wish to add custom Winit integrations.
188/// If you want to process events for your app or game, you should instead use
189/// `bevy::window::WindowEvent`, or one of its sub-events.
190///
191/// When you receive this event it has already been handled by Bevy's main loop.
192/// Sending these events will NOT cause them to be processed by Bevy.
193#[derive(Debug, Clone, Message)]
194pub struct RawWinitWindowEvent {
195 /// The window for which the event was fired.
196 pub window_id: WindowId,
197 /// The raw winit window event.
198 pub event: winit::event::WindowEvent,
199}
200
201/// A wrapper type around [`winit::event_loop::EventLoopProxy`] with the specific
202/// [`winit::event::Event::UserEvent`] used in the [`WinitPlugin`].
203///
204/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
205///
206/// Use `Res<EventLoopProxyWrapper>` to retrieve this resource.
207#[derive(Resource, Deref)]
208pub struct EventLoopProxyWrapper(EventLoopProxy<WinitUserEvent>);
209
210/// A wrapper around [`winit::event_loop::OwnedDisplayHandle`]
211///
212/// The `DisplayHandleWrapper` can be used to build integrations that rely on direct
213/// access to the display handle
214///
215/// Use `Res<DisplayHandleWrapper>` to receive this resource.
216#[derive(Resource, Deref)]
217pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle);
218
219trait AppSendEvent {
220 fn send(&mut self, event: impl Into<WindowEvent>);
221}
222
223impl AppSendEvent for Vec<WindowEvent> {
224 fn send(&mut self, event: impl Into<WindowEvent>) {
225 self.push(Into::<WindowEvent>::into(event));
226 }
227}
228
229/// The parameters of the [`create_windows`] system.
230pub type CreateWindowParams<'w, 's> = (
231 Commands<'w, 's>,
232 Query<
233 'w,
234 's,
235 (
236 Entity,
237 &'static mut Window,
238 &'static CursorOptions,
239 Option<&'static RawHandleWrapperHolder>,
240 ),
241 Added<Window>,
242 >,
243 MessageWriter<'w, WindowCreated>,
244 ResMut<'w, WinitActionRequestHandlers>,
245 Res<'w, AccessibilityRequested>,
246 Res<'w, WinitMonitors>,
247);
248
249/// The parameters of the [`create_monitors`] system.
250pub type CreateMonitorParams<'w, 's> = (Commands<'w, 's>, ResMut<'w, WinitMonitors>);