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