Skip to main content

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::{ExitSystems, 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::{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                    changed_windows,
140                    changed_cursor_options,
141                    despawn_windows.after(ExitSystems),
142                    check_keyboard_focus_lost,
143                )
144                    .chain(),
145            );
146
147        app.add_plugins(AccessKitPlugin);
148        app.add_plugins(cursor::WinitCursorPlugin);
149
150        app.add_observer(
151            |_window: On<Add, Window>, event_loop_proxy: Res<EventLoopProxyWrapper>| -> Result {
152                event_loop_proxy.send_event(WinitUserEvent::WindowAdded)?;
153
154                Ok(())
155            },
156        );
157    }
158}
159
160/// Events that can be sent to perform actions inside the winit event loop.
161///
162/// Sent via the [`EventLoopProxyWrapper`] resource.
163///
164/// # Example
165///
166/// ```
167/// # use bevy_ecs::prelude::*;
168/// # use bevy_winit::{EventLoopProxyWrapper, WinitUserEvent};
169/// fn wakeup_system(event_loop_proxy: Res<EventLoopProxyWrapper>) -> Result {
170///     event_loop_proxy.send_event(WinitUserEvent::WakeUp)?;
171///
172///     Ok(())
173/// }
174/// ```
175#[derive(Debug, Clone, Copy, Reflect)]
176#[reflect(Debug, Clone)]
177pub enum WinitUserEvent {
178    /// Dummy event that just wakes up the winit event loop
179    WakeUp,
180    /// Tell winit that a window needs to be created
181    WindowAdded,
182}
183
184/// The original window event as produced by Winit. This is meant as an escape
185/// hatch for power users that wish to add custom Winit integrations.
186/// If you want to process events for your app or game, you should instead use
187/// `bevy::window::WindowEvent`, or one of its sub-events.
188///
189/// When you receive this event it has already been handled by Bevy's main loop.
190/// Sending these events will NOT cause them to be processed by Bevy.
191#[derive(Debug, Clone, Message)]
192pub struct RawWinitWindowEvent {
193    /// The window for which the event was fired.
194    pub window_id: WindowId,
195    /// The raw winit window event.
196    pub event: winit::event::WindowEvent,
197}
198
199/// A wrapper type around [`winit::event_loop::EventLoopProxy`] with the specific
200/// [`winit::event::Event::UserEvent`] used in the [`WinitPlugin`].
201///
202/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
203///
204/// Use `Res<EventLoopProxyWrapper>` to retrieve this resource.
205#[derive(Resource, Deref)]
206pub struct EventLoopProxyWrapper(EventLoopProxy<WinitUserEvent>);
207
208/// A wrapper around [`winit::event_loop::OwnedDisplayHandle`]
209///
210/// The `DisplayHandleWrapper` can be used to build integrations that rely on direct
211/// access to the display handle
212///
213/// Use `Res<DisplayHandleWrapper>` to receive this resource.
214#[derive(Resource, Deref)]
215pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle);
216
217trait AppSendEvent {
218    fn send(&mut self, event: impl Into<WindowEvent>);
219}
220
221impl AppSendEvent for Vec<WindowEvent> {
222    fn send(&mut self, event: impl Into<WindowEvent>) {
223        self.push(Into::<WindowEvent>::into(event));
224    }
225}
226
227/// The parameters of the [`create_windows`] system.
228pub type CreateWindowParams<'w, 's> = (
229    Commands<'w, 's>,
230    Query<
231        'w,
232        's,
233        (
234            Entity,
235            &'static mut Window,
236            &'static CursorOptions,
237            Option<&'static RawHandleWrapperHolder>,
238        ),
239        Added<Window>,
240    >,
241    MessageWriter<'w, WindowCreated>,
242    ResMut<'w, WinitActionRequestHandlers>,
243    Res<'w, AccessibilityRequested>,
244    Res<'w, WinitMonitors>,
245);
246
247/// The parameters of the [`create_monitors`] system.
248pub type CreateMonitorParams<'w, 's> = (Commands<'w, 's>, ResMut<'w, WinitMonitors>);