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