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, OnAppExitSystems, 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#[derive(Default)]
66pub struct WinitPlugin {
67    /// Allows the window (and the event loop) to be created on any thread
68    /// instead of only the main thread.
69    ///
70    /// See [`EventLoopBuilder::build`](winit::event_loop::EventLoopBuilder::build) for more information on this.
71    ///
72    /// # Supported platforms
73    ///
74    /// Only works on Linux (X11/Wayland) and Windows.
75    /// This field is ignored on other platforms.
76    pub run_on_any_thread: bool,
77}
78
79impl Plugin for WinitPlugin {
80    fn name(&self) -> &str {
81        "bevy_winit::WinitPlugin"
82    }
83
84    fn build(&self, app: &mut App) {
85        let mut event_loop_builder = EventLoop::<WinitUserEvent>::with_user_event();
86
87        // linux check is needed because x11 might be enabled on other platforms.
88        #[cfg(all(target_os = "linux", feature = "x11"))]
89        {
90            use winit::platform::x11::EventLoopBuilderExtX11;
91
92            // This allows a Bevy app to be started and ran outside the main thread.
93            // A use case for this is to allow external applications to spawn a thread
94            // which runs a Bevy app without requiring the Bevy app to need to reside on
95            // the main thread, which can be problematic.
96            event_loop_builder.with_any_thread(self.run_on_any_thread);
97        }
98
99        // linux check is needed because wayland might be enabled on other platforms.
100        #[cfg(all(target_os = "linux", feature = "wayland"))]
101        {
102            use winit::platform::wayland::EventLoopBuilderExtWayland;
103            event_loop_builder.with_any_thread(self.run_on_any_thread);
104        }
105
106        #[cfg(target_os = "macos")]
107        {
108            use bevy_ecs::system::SystemState;
109            use winit::platform::macos::EventLoopBuilderExtMacOS;
110
111            // Don't request app activation on startup if all its windows should
112            // start unfocused. Otherwise, app activation would focus one of the
113            // windows.
114            let mut initial_windows_state =
115                SystemState::<Query<(Entity, &Window)>>::new(app.world_mut());
116            let initial_windows = initial_windows_state.get(app.world()).unwrap();
117            let initially_focused = initial_windows.iter().any(|(_, window)| window.focused);
118            event_loop_builder.with_activate_ignoring_other_apps(initially_focused);
119        }
120
121        #[cfg(target_os = "windows")]
122        {
123            use winit::platform::windows::EventLoopBuilderExtWindows;
124            event_loop_builder.with_any_thread(self.run_on_any_thread);
125        }
126
127        #[cfg(target_os = "android")]
128        {
129            use winit::platform::android::EventLoopBuilderExtAndroid;
130            let msg = "Bevy must be setup with the #[bevy_main] macro on Android";
131            event_loop_builder
132                .with_android_app(bevy_android::ANDROID_APP.get().expect(msg).clone());
133        }
134
135        let event_loop = event_loop_builder
136            .build()
137            .expect("Failed to build event loop");
138
139        let event_loop_proxy = event_loop.create_proxy();
140
141        // Wake up the event loop when `Ctrl+C` is received so that the app can
142        // exit even while idle in a reactive update mode
143        #[cfg(any(all(unix, not(target_os = "horizon")), windows))]
144        {
145            let event_loop_proxy = event_loop_proxy.clone();
146            bevy_app::TerminalCtrlCHandlerPlugin::register_exit_handler(move || {
147                let _ = event_loop_proxy.send_event(WinitUserEvent::WakeUp);
148            });
149        }
150
151        app.init_resource::<WinitMonitors>()
152            .init_resource::<WinitSettings>()
153            .insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle()))
154            .insert_resource(EventLoopProxyWrapper(event_loop_proxy))
155            .add_message::<RawWinitWindowEvent>()
156            .set_runner(|app| winit_runner(app, event_loop))
157            .add_systems(
158                Last,
159                (
160                    changed_windows,
161                    changed_cursor_options,
162                    despawn_windows.after(ExitSystems).after(OnAppExitSystems),
163                    check_keyboard_focus_lost,
164                )
165                    .chain(),
166            );
167
168        app.add_plugins(AccessKitPlugin);
169        app.add_plugins(cursor::WinitCursorPlugin);
170
171        app.add_observer(
172            |_window: On<Add<Window>>, event_loop_proxy: Res<EventLoopProxyWrapper>| -> Result {
173                event_loop_proxy.send_event(WinitUserEvent::WindowAdded)?;
174
175                Ok(())
176            },
177        );
178    }
179}
180
181/// Events that can be sent to perform actions inside the winit event loop.
182///
183/// Sent via the [`EventLoopProxyWrapper`] resource.
184///
185/// # Example
186///
187/// ```
188/// # use bevy_ecs::prelude::*;
189/// # use bevy_winit::{EventLoopProxyWrapper, WinitUserEvent};
190/// fn wakeup_system(event_loop_proxy: Res<EventLoopProxyWrapper>) -> Result {
191///     event_loop_proxy.send_event(WinitUserEvent::WakeUp)?;
192///
193///     Ok(())
194/// }
195/// ```
196#[derive(Debug, Clone, Copy, Reflect)]
197#[reflect(Debug, Clone)]
198pub enum WinitUserEvent {
199    /// Dummy event that just wakes up the winit event loop
200    WakeUp,
201    /// Tell winit that a window needs to be created
202    WindowAdded,
203}
204
205/// The original window event as produced by Winit. This is meant as an escape
206/// hatch for power users that wish to add custom Winit integrations.
207/// If you want to process events for your app or game, you should instead use
208/// `bevy::window::WindowEvent`, or one of its sub-events.
209///
210/// When you receive this event it has already been handled by Bevy's main loop.
211/// Sending these events will NOT cause them to be processed by Bevy.
212#[derive(Debug, Clone, Message)]
213pub struct RawWinitWindowEvent {
214    /// The window for which the event was fired.
215    pub window_id: WindowId,
216    /// The raw winit window event.
217    pub event: winit::event::WindowEvent,
218}
219
220/// A wrapper type around [`winit::event_loop::EventLoopProxy`] with the specific
221/// [`winit::event::Event::UserEvent`] used in the [`WinitPlugin`].
222///
223/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
224///
225/// Use `Res<EventLoopProxyWrapper>` to retrieve this resource.
226#[derive(Resource, Deref)]
227pub struct EventLoopProxyWrapper(EventLoopProxy<WinitUserEvent>);
228
229/// A wrapper around [`winit::event_loop::OwnedDisplayHandle`]
230///
231/// The `DisplayHandleWrapper` can be used to build integrations that rely on direct
232/// access to the display handle
233///
234/// Use `Res<DisplayHandleWrapper>` to receive this resource.
235#[derive(Resource, Deref)]
236pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle);
237
238trait AppSendEvent {
239    fn send(&mut self, event: impl Into<WindowEvent>);
240}
241
242impl AppSendEvent for Vec<WindowEvent> {
243    fn send(&mut self, event: impl Into<WindowEvent>) {
244        self.push(Into::<WindowEvent>::into(event));
245    }
246}
247
248/// The parameters of the [`create_windows`] system.
249pub type CreateWindowParams<'w, 's> = (
250    Commands<'w, 's>,
251    Query<
252        'w,
253        's,
254        (
255            Entity,
256            &'static mut Window,
257            &'static CursorOptions,
258            Option<&'static RawHandleWrapperHolder>,
259        ),
260        Added<Window>,
261    >,
262    MessageWriter<'w, WindowCreated>,
263    ResMut<'w, WinitActionRequestHandlers>,
264    Res<'w, AccessibilityRequested>,
265    Res<'w, WinitMonitors>,
266);
267
268/// The parameters of the [`create_monitors`] system.
269pub type CreateMonitorParams<'w, 's> = (Commands<'w, 's>, ResMut<'w, WinitMonitors>);