1use std::collections::HashMap;
2
3use bevy_derive::{Deref, DerefMut};
4use bevy_ecs::{
5 change_detection::DetectChangesMut,
6 entity::Entity,
7 lifecycle::RemovedComponents,
8 message::MessageWriter,
9 prelude::{Changed, Component},
10 system::{Local, NonSendMarker, Query, SystemParamItem},
11};
12use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput};
13use bevy_window::{
14 ClosingWindow, CursorOptions, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window,
15 WindowClosed, WindowClosing, WindowCreated, WindowEvent, WindowFocused, WindowMode,
16 WindowResized, WindowWrapper,
17};
18use tracing::{error, info, warn};
19
20use winit::{
21 dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
22 event_loop::ActiveEventLoop,
23};
24
25use bevy_app::AppExit;
26use bevy_ecs::{prelude::MessageReader, query::With, system::Res};
27use bevy_math::{IVec2, UVec2};
28#[cfg(target_os = "ios")]
29use winit::platform::ios::WindowExtIOS;
30#[cfg(target_arch = "wasm32")]
31use winit::platform::web::WindowExtWebSys;
32
33use crate::{
34 accessibility::ACCESS_KIT_ADAPTERS,
35 converters::{
36 convert_enabled_buttons, convert_resize_direction, convert_window_level,
37 convert_window_theme, convert_winit_theme,
38 },
39 get_selected_videomode, select_monitor,
40 state::react_to_resize,
41 winit_monitors::WinitMonitors,
42 CreateMonitorParams, CreateWindowParams, WINIT_WINDOWS,
43};
44
45pub fn create_windows(
51 event_loop: &ActiveEventLoop,
52 (
53 mut commands,
54 mut created_windows,
55 mut window_created_events,
56 mut handlers,
57 accessibility_requested,
58 monitors,
59 ): SystemParamItem<CreateWindowParams>,
60) {
61 WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
62 ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
63 for (entity, mut window, cursor_options, handle_holder) in &mut created_windows {
64 if winit_windows.get_window(entity).is_some() {
65 continue;
66 }
67
68 info!("Creating new window {} ({})", window.title.as_str(), entity);
69
70 let winit_window = winit_windows.create_window(
71 event_loop,
72 entity,
73 &window,
74 cursor_options,
75 adapters,
76 &mut handlers,
77 &accessibility_requested,
78 &monitors,
79 );
80
81 if let Some(theme) = winit_window.theme() {
82 window.window_theme = Some(convert_winit_theme(theme));
83 }
84
85 window
86 .resolution
87 .set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32);
88
89 commands.entity(entity).insert((
90 CachedWindow(window.clone()),
91 CachedCursorOptions(cursor_options.clone()),
92 WinitWindowPressedKeys::default(),
93 ));
94
95 if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) {
96 commands.entity(entity).insert(handle_wrapper.clone());
97 if let Some(handle_holder) = handle_holder {
98 *handle_holder.0.lock().unwrap() = Some(handle_wrapper);
99 }
100 }
101
102 #[cfg(target_arch = "wasm32")]
103 {
104 if window.fit_canvas_to_parent {
105 let canvas = winit_window
106 .canvas()
107 .expect("window.canvas() can only be called in main thread.");
108 let style = canvas.style();
109 style.set_property("width", "100%").unwrap();
110 style.set_property("height", "100%").unwrap();
111 }
112 }
113
114 #[cfg(target_os = "ios")]
115 {
116 winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture);
117 winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture);
118 winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture);
119 if let Some((min, max)) = window.recognize_pan_gesture {
120 winit_window.recognize_pan_gesture(true, min, max);
121 } else {
122 winit_window.recognize_pan_gesture(false, 0, 0);
123 }
124 }
125
126 window_created_events.write(WindowCreated { window: entity });
127 }
128 });
129 });
130}
131
132pub(crate) fn check_keyboard_focus_lost(
135 mut window_focused_reader: MessageReader<WindowFocused>,
136 mut keyboard_focus_lost_writer: MessageWriter<KeyboardFocusLost>,
137 mut keyboard_input_writer: MessageWriter<KeyboardInput>,
138 mut window_event_writer: MessageWriter<WindowEvent>,
139 mut q_windows: Query<&mut WinitWindowPressedKeys>,
140) {
141 let mut focus_lost = vec![];
142 let mut focus_gained = false;
143 for e in window_focused_reader.read() {
144 if e.focused {
145 focus_gained = true;
146 } else {
147 focus_lost.push(e.window);
148 }
149 }
150
151 if !focus_gained {
152 if !focus_lost.is_empty() {
153 window_event_writer.write(WindowEvent::KeyboardFocusLost(KeyboardFocusLost));
154 keyboard_focus_lost_writer.write(KeyboardFocusLost);
155 }
156
157 for window in focus_lost {
158 let Ok(mut pressed_keys) = q_windows.get_mut(window) else {
159 continue;
160 };
161 for (key_code, logical_key) in pressed_keys.0.drain() {
162 let event = KeyboardInput {
163 key_code,
164 logical_key,
165 state: bevy_input::ButtonState::Released,
166 repeat: false,
167 window,
168 text: None,
169 };
170 window_event_writer.write(WindowEvent::KeyboardInput(event.clone()));
171 keyboard_input_writer.write(event);
172 }
173 }
174 }
175}
176
177pub fn create_monitors(
179 event_loop: &ActiveEventLoop,
180 (mut commands, mut monitors): SystemParamItem<CreateMonitorParams>,
181) {
182 let primary_monitor = event_loop.primary_monitor();
183 let mut seen_monitors = vec![false; monitors.monitors.len()];
184
185 'outer: for monitor in event_loop.available_monitors() {
186 for (idx, (m, _)) in monitors.monitors.iter().enumerate() {
187 if &monitor == m {
188 seen_monitors[idx] = true;
189 continue 'outer;
190 }
191 }
192
193 let size = monitor.size();
194 let position = monitor.position();
195
196 let entity = commands
197 .spawn(Monitor {
198 name: monitor.name(),
199 physical_height: size.height,
200 physical_width: size.width,
201 physical_position: IVec2::new(position.x, position.y),
202 refresh_rate_millihertz: monitor.refresh_rate_millihertz(),
203 scale_factor: monitor.scale_factor(),
204 video_modes: monitor
205 .video_modes()
206 .map(|v| {
207 let size = v.size();
208 VideoMode {
209 physical_size: UVec2::new(size.width, size.height),
210 bit_depth: v.bit_depth(),
211 refresh_rate_millihertz: v.refresh_rate_millihertz(),
212 }
213 })
214 .collect(),
215 })
216 .id();
217
218 if primary_monitor.as_ref() == Some(&monitor) {
219 commands.entity(entity).insert(PrimaryMonitor);
220 }
221
222 seen_monitors.push(true);
223 monitors.monitors.push((monitor, entity));
224 }
225
226 let mut idx = 0;
227 monitors.monitors.retain(|(_m, entity)| {
228 if seen_monitors[idx] {
229 idx += 1;
230 true
231 } else {
232 info!("Monitor removed {}", entity);
233 commands.entity(*entity).despawn();
234 idx += 1;
235 false
236 }
237 });
238}
239
240pub(crate) fn despawn_windows(
241 closing: Query<Entity, With<ClosingWindow>>,
242 mut closed: RemovedComponents<Window>,
243 window_entities: Query<Entity, With<Window>>,
244 mut closing_event_writer: MessageWriter<WindowClosing>,
245 mut closed_event_writer: MessageWriter<WindowClosed>,
246 mut windows_to_drop: Local<Vec<WindowWrapper<winit::window::Window>>>,
247 mut exit_event_reader: MessageReader<AppExit>,
248 _non_send_marker: NonSendMarker,
249) {
250 windows_to_drop.clear();
252 for window in closing.iter() {
253 closing_event_writer.write(WindowClosing { window });
254 }
255 for window in closed.read() {
256 info!("Closing window {}", window);
257 if !window_entities.contains(window) {
261 WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
262 if let Some(window) = winit_windows.remove_window(window) {
263 windows_to_drop.push(window);
268 }
269 });
270 closed_event_writer.write(WindowClosed { window });
271 }
272 }
273
274 if !exit_event_reader.is_empty() {
277 exit_event_reader.clear();
278 for window in window_entities.iter() {
279 closing_event_writer.write(WindowClosing { window });
280 }
281 }
282}
283
284#[derive(Debug, Clone, Component, Deref, DerefMut)]
286pub(crate) struct CachedWindow(Window);
287
288#[derive(Debug, Clone, Component, Deref, DerefMut)]
290pub(crate) struct CachedCursorOptions(CursorOptions);
291
292pub(crate) fn changed_windows(
301 mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed<Window>>,
302 monitors: Res<WinitMonitors>,
303 mut window_resized: MessageWriter<WindowResized>,
304 _non_send_marker: NonSendMarker,
305) {
306 WINIT_WINDOWS.with_borrow(|winit_windows| {
307 for (entity, mut window, mut cache) in &mut changed_windows {
308 let Some(winit_window) = winit_windows.get_window(entity) else {
309 continue;
310 };
311
312 if window.title != cache.title {
313 winit_window.set_title(window.title.as_str());
314 }
315
316 if window.mode != cache.mode {
317 let new_mode = match window.mode {
318 WindowMode::BorderlessFullscreen(monitor_selection) => {
319 Some(Some(winit::window::Fullscreen::Borderless(select_monitor(
320 &monitors,
321 winit_window.primary_monitor(),
322 winit_window.current_monitor(),
323 &monitor_selection,
324 ))))
325 }
326 WindowMode::Fullscreen(monitor_selection, video_mode_selection) => {
327 let monitor = &select_monitor(
328 &monitors,
329 winit_window.primary_monitor(),
330 winit_window.current_monitor(),
331 &monitor_selection,
332 )
333 .unwrap_or_else(|| {
334 panic!("Could not find monitor for {monitor_selection:?}")
335 });
336
337 if let Some(video_mode) = get_selected_videomode(monitor, &video_mode_selection)
338 {
339 Some(Some(winit::window::Fullscreen::Exclusive(video_mode)))
340 } else {
341 warn!(
342 "Could not find valid fullscreen video mode for {:?} {:?}",
343 monitor_selection, video_mode_selection
344 );
345 None
346 }
347 }
348 WindowMode::Windowed => Some(None),
349 };
350
351 if let Some(new_mode) = new_mode
352 && winit_window.fullscreen() != new_mode {
353 winit_window.set_fullscreen(new_mode);
354 }
355 }
356
357 if window.resolution != cache.resolution {
358 let mut physical_size = PhysicalSize::new(
359 window.resolution.physical_width(),
360 window.resolution.physical_height(),
361 );
362
363 let cached_physical_size = PhysicalSize::new(
364 cache.physical_width(),
365 cache.physical_height(),
366 );
367
368 let base_scale_factor = window.resolution.base_scale_factor();
369
370 let scale_factor = window.scale_factor();
373 let cached_scale_factor = cache.scale_factor();
374
375 if scale_factor != cached_scale_factor && !winit_window.is_maximized() {
377 let logical_size =
378 if let Some(cached_factor) = cache.resolution.scale_factor_override() {
379 physical_size.to_logical::<f32>(cached_factor as f64)
380 } else {
381 physical_size.to_logical::<f32>(base_scale_factor as f64)
382 };
383
384 if let Some(forced_factor) = window.resolution.scale_factor_override() {
386 physical_size = logical_size.to_physical::<u32>(forced_factor as f64);
390 } else {
391 physical_size = logical_size.to_physical::<u32>(base_scale_factor as f64);
392 }
393 }
394
395 if physical_size != cached_physical_size
396 && let Some(new_physical_size) = winit_window.request_inner_size(physical_size) {
397 react_to_resize(entity, &mut window, new_physical_size, &mut window_resized);
398 }
399 }
400
401 if window.physical_cursor_position() != cache.physical_cursor_position()
402 && let Some(physical_position) = window.physical_cursor_position() {
403 let position = PhysicalPosition::new(physical_position.x, physical_position.y);
404
405 if let Err(err) = winit_window.set_cursor_position(position) {
406 error!("could not set cursor position: {}", err);
407 }
408 }
409
410 if window.decorations != cache.decorations
411 && window.decorations != winit_window.is_decorated()
412 {
413 winit_window.set_decorations(window.decorations);
414 }
415
416 if window.resizable != cache.resizable
417 && window.resizable != winit_window.is_resizable()
418 {
419 winit_window.set_resizable(window.resizable);
420 }
421
422 if window.enabled_buttons != cache.enabled_buttons {
423 winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons));
424 }
425
426 if window.resize_constraints != cache.resize_constraints {
427 let constraints = window.resize_constraints.check_constraints();
428 let min_inner_size = LogicalSize {
429 width: constraints.min_width,
430 height: constraints.min_height,
431 };
432 let max_inner_size = LogicalSize {
433 width: constraints.max_width,
434 height: constraints.max_height,
435 };
436
437 winit_window.set_min_inner_size(Some(min_inner_size));
438 if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
439 winit_window.set_max_inner_size(Some(max_inner_size));
440 }
441 }
442
443 if window.position != cache.position
444 && let Some(position) = crate::winit_window_position(
445 &window.position,
446 &window.resolution,
447 &monitors,
448 winit_window.primary_monitor(),
449 winit_window.current_monitor(),
450 ) {
451 let should_set = match winit_window.outer_position() {
452 Ok(current_position) => current_position != position,
453 _ => true,
454 };
455
456 if should_set {
457 winit_window.set_outer_position(position);
458 }
459 }
460
461 if let Some(maximized) = window.internal.take_maximize_request() {
462 winit_window.set_maximized(maximized);
463 }
464
465 if let Some(minimized) = window.internal.take_minimize_request() {
466 winit_window.set_minimized(minimized);
467 }
468
469 if window.internal.take_move_request()
470 && let Err(e) = winit_window.drag_window() {
471 warn!("Winit returned an error while attempting to drag the window: {e}");
472 }
473
474 if let Some(resize_direction) = window.internal.take_resize_request()
475 && let Err(e) =
476 winit_window.drag_resize_window(convert_resize_direction(resize_direction))
477 {
478 warn!("Winit returned an error while attempting to drag resize the window: {e}");
479 }
480
481 if window.focused != cache.focused && window.focused {
482 winit_window.focus_window();
483 }
484
485 if window.window_level != cache.window_level {
486 winit_window.set_window_level(convert_window_level(window.window_level));
487 }
488
489 if window.transparent != cache.transparent {
491 window.transparent = cache.transparent;
492 warn!("Winit does not currently support updating transparency after window creation.");
493 }
494
495 #[cfg(target_arch = "wasm32")]
496 if window.canvas != cache.canvas {
497 window.canvas.clone_from(&cache.canvas);
498 warn!(
499 "Bevy currently doesn't support modifying the window canvas after initialization."
500 );
501 }
502
503 if window.ime_enabled != cache.ime_enabled {
504 winit_window.set_ime_allowed(window.ime_enabled);
505 }
506
507 if window.ime_position != cache.ime_position {
508 winit_window.set_ime_cursor_area(
509 LogicalPosition::new(window.ime_position.x, window.ime_position.y),
510 PhysicalSize::new(10, 10),
511 );
512 }
513
514 if window.window_theme != cache.window_theme {
515 winit_window.set_theme(window.window_theme.map(convert_window_theme));
516 }
517
518 if window.visible != cache.visible {
519 winit_window.set_visible(window.visible);
520 }
521
522 #[cfg(target_os = "ios")]
523 {
524 if window.recognize_pinch_gesture != cache.recognize_pinch_gesture {
525 winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture);
526 }
527 if window.recognize_rotation_gesture != cache.recognize_rotation_gesture {
528 winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture);
529 }
530 if window.recognize_doubletap_gesture != cache.recognize_doubletap_gesture {
531 winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture);
532 }
533 if window.recognize_pan_gesture != cache.recognize_pan_gesture {
534 match (
535 window.recognize_pan_gesture,
536 cache.recognize_pan_gesture,
537 ) {
538 (Some(_), Some(_)) => {
539 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");
540 }
541 (Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max),
542 _ => winit_window.recognize_pan_gesture(false, 0, 0),
543 }
544 }
545
546 if window.prefers_home_indicator_hidden != cache.prefers_home_indicator_hidden {
547 winit_window
548 .set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden);
549 }
550 if window.prefers_status_bar_hidden != cache.prefers_status_bar_hidden {
551 winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden);
552 }
553 if window.preferred_screen_edges_deferring_system_gestures
554 != cache
555 .preferred_screen_edges_deferring_system_gestures
556 {
557 use crate::converters::convert_screen_edge;
558 let preferred_edge =
559 convert_screen_edge(window.preferred_screen_edges_deferring_system_gestures);
560 winit_window.set_preferred_screen_edges_deferring_system_gestures(preferred_edge);
561 }
562 }
563 **cache = window.clone();
564 }
565 });
566}
567
568pub(crate) fn changed_cursor_options(
569 mut changed_windows: Query<
570 (
571 Entity,
572 &Window,
573 &mut CursorOptions,
574 &mut CachedCursorOptions,
575 ),
576 Changed<CursorOptions>,
577 >,
578 _non_send_marker: NonSendMarker,
579) {
580 WINIT_WINDOWS.with_borrow(|winit_windows| {
581 for (entity, window, mut cursor_options, mut cache) in &mut changed_windows {
582 let cursor_options = cursor_options.bypass_change_detection();
584 let Some(winit_window) = winit_windows.get_window(entity) else {
585 continue;
586 };
587 if let Err(err) =
589 crate::winit_windows::attempt_grab(winit_window, cursor_options.grab_mode)
590 {
591 warn!(
592 "Could not set cursor grab mode for window {}: {}",
593 window.title, err
594 );
595 cursor_options.grab_mode = cache.grab_mode;
596 } else {
597 cache.grab_mode = cursor_options.grab_mode;
598 }
599
600 if cursor_options.visible != cache.visible {
601 winit_window.set_cursor_visible(cursor_options.visible);
602 cache.visible = cursor_options.visible;
603 }
604
605 if cursor_options.hit_test != cache.hit_test {
606 if let Err(err) = winit_window.set_cursor_hittest(cursor_options.hit_test) {
607 warn!(
608 "Could not set cursor hit test for window {}: {}",
609 window.title, err
610 );
611 cursor_options.hit_test = cache.hit_test;
612 } else {
613 cache.hit_test = cursor_options.hit_test;
614 }
615 }
616 }
617 });
618}
619
620#[derive(Default, Component)]
623pub struct WinitWindowPressedKeys(pub(crate) HashMap<KeyCode, Key>);