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