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, HasWindows, Monitor, OnMonitor, PrimaryMonitor, RawHandleWrapper,
15 VideoMode, Window, WindowClosed, WindowClosing, WindowCreated, WindowEvent, WindowFocused,
16 WindowMode, WindowResized, WindowScaleFactorChanged, 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 resolve_exclusive_fullscreen, 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 #[cfg(target_os = "macos")]
126 {
127 if window.focused {
129 winit_window.focus_window();
130 }
131 }
132
133 window_created_events.write(WindowCreated { window: entity });
134 }
135 });
136 });
137}
138
139pub(crate) fn check_keyboard_focus_lost(
142 mut window_focused_reader: MessageReader<WindowFocused>,
143 mut keyboard_focus_lost_writer: MessageWriter<KeyboardFocusLost>,
144 mut keyboard_input_writer: MessageWriter<KeyboardInput>,
145 mut window_event_writer: MessageWriter<WindowEvent>,
146 mut q_windows: Query<&mut WinitWindowPressedKeys>,
147) {
148 let mut focus_lost = vec![];
149 let mut focus_gained = false;
150 for e in window_focused_reader.read() {
151 if e.focused {
152 focus_gained = true;
153 } else {
154 focus_lost.push(e.window);
155 }
156 }
157
158 if !focus_gained {
159 if !focus_lost.is_empty() {
160 window_event_writer.write(WindowEvent::KeyboardFocusLost(KeyboardFocusLost));
161 keyboard_focus_lost_writer.write(KeyboardFocusLost);
162 }
163
164 for window in focus_lost {
165 let Ok(mut pressed_keys) = q_windows.get_mut(window) else {
166 continue;
167 };
168 for (key_code, logical_key) in pressed_keys.0.drain() {
169 let event = KeyboardInput {
170 key_code,
171 logical_key,
172 state: bevy_input::ButtonState::Released,
173 repeat: false,
174 window,
175 text: None,
176 };
177 window_event_writer.write(WindowEvent::KeyboardInput(event.clone()));
178 keyboard_input_writer.write(event);
179 }
180 }
181 }
182}
183
184pub fn create_monitors(
186 event_loop: &ActiveEventLoop,
187 (mut commands, mut monitors): SystemParamItem<CreateMonitorParams>,
188) {
189 let primary_monitor = event_loop.primary_monitor();
190 let mut seen_monitors = vec![false; monitors.monitors.len()];
191
192 'outer: for monitor in event_loop.available_monitors() {
193 for (idx, (m, _)) in monitors.monitors.iter().enumerate() {
194 if &monitor == m {
195 seen_monitors[idx] = true;
196 continue 'outer;
197 }
198 }
199
200 let size = monitor.size();
201 let position = monitor.position();
202
203 let entity = commands
204 .spawn(Monitor {
205 name: monitor.name(),
206 physical_height: size.height,
207 physical_width: size.width,
208 physical_position: IVec2::new(position.x, position.y),
209 refresh_rate_millihertz: monitor.refresh_rate_millihertz(),
210 scale_factor: monitor.scale_factor(),
211 video_modes: monitor
212 .video_modes()
213 .map(|v| {
214 let size = v.size();
215 VideoMode {
216 physical_size: UVec2::new(size.width, size.height),
217 bit_depth: v.bit_depth(),
218 refresh_rate_millihertz: v.refresh_rate_millihertz(),
219 }
220 })
221 .collect(),
222 })
223 .id();
224
225 if primary_monitor.as_ref() == Some(&monitor) {
226 commands.entity(entity).insert(PrimaryMonitor);
227 }
228
229 seen_monitors.push(true);
230 monitors.monitors.push((monitor, entity));
231 }
232
233 let mut idx = 0;
234 monitors.monitors.retain(|(_m, entity)| {
235 if seen_monitors[idx] {
236 idx += 1;
237 true
238 } else {
239 info!("Monitor removed {}", entity);
240
241 commands
242 .entity(*entity)
243 .remove::<HasWindows>()
246 .despawn();
247
248 idx += 1;
249 false
250 }
251 });
252}
253
254pub(crate) fn despawn_windows(
255 closing: Query<Entity, With<ClosingWindow>>,
256 mut closed: RemovedComponents<Window>,
257 window_entities: Query<Entity, With<Window>>,
258 mut closing_event_writer: MessageWriter<WindowClosing>,
259 mut closed_event_writer: MessageWriter<WindowClosed>,
260 mut windows_to_drop: Local<Vec<WindowWrapper<winit::window::Window>>>,
261 mut exit_event_reader: MessageReader<AppExit>,
262 _non_send_marker: NonSendMarker,
263) {
264 windows_to_drop.clear();
266 for window in closing.iter() {
267 closing_event_writer.write(WindowClosing { window });
268 }
269 for window in closed.read() {
270 info!("Closing window {}", window);
271 if !window_entities.contains(window) {
275 WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
276 if let Some(window) = winit_windows.remove_window(window) {
277 windows_to_drop.push(window);
282 }
283 });
284 closed_event_writer.write(WindowClosed { window });
285 }
286 }
287
288 if !exit_event_reader.is_empty() {
292 exit_event_reader.clear();
293 WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
294 for window in window_entities.iter() {
295 closing_event_writer.write(WindowClosing { window });
296 if let Some(wrapper) = winit_windows.remove_window(window) {
297 windows_to_drop.push(wrapper);
298 }
299 }
300 });
301 }
302}
303
304#[derive(Debug, Clone, Component, Deref, DerefMut)]
306pub(crate) struct CachedWindow(Window);
307
308#[derive(Debug, Clone, Component, Deref, DerefMut)]
310pub(crate) struct CachedCursorOptions(CursorOptions);
311
312pub(crate) fn changed_windows(
321 mut commands: Commands,
322 mut changed_windows: Query<
323 (Entity, &mut Window, &mut CachedWindow, Option<&OnMonitor>),
324 Changed<Window>,
325 >,
326 monitors: Res<WinitMonitors>,
327 mut window_resized: MessageWriter<WindowResized>,
328 mut window_event: MessageWriter<WindowEvent>,
329 mut window_rescaled: MessageWriter<WindowScaleFactorChanged>,
330 _non_send_marker: NonSendMarker,
331) {
332 WINIT_WINDOWS.with_borrow(|winit_windows| {
333 for (entity, mut window, mut cache, monitor_relationship) in &mut changed_windows {
334 let Some(winit_window) = winit_windows.get_window(entity) else {
335 continue;
336 };
337
338 if window.title != cache.title {
339 winit_window.set_title(window.title.as_str());
340 }
341
342 if window.mode != cache.mode {
343 let new_mode = match window.mode {
344 WindowMode::BorderlessFullscreen(monitor_selection) => {
345 Some(Some(winit::window::Fullscreen::Borderless(select_monitor(
346 &monitors,
347 winit_window.primary_monitor(),
348 winit_window.current_monitor(),
349 &monitor_selection,
350 ))))
351 }
352 WindowMode::Fullscreen(monitor_selection, video_mode_selection) => {
353 let monitor = select_monitor(
354 &monitors,
355 winit_window.primary_monitor(),
356 winit_window.current_monitor(),
357 &monitor_selection,
358 );
359 Some(Some(resolve_exclusive_fullscreen(
360 monitor,
361 monitor_selection,
362 video_mode_selection,
363 )))
364 }
365 WindowMode::Windowed => Some(None),
366 };
367
368 if let Some(new_mode) = new_mode
369 && winit_window.fullscreen() != new_mode {
370 winit_window.set_fullscreen(new_mode);
371 }
372 }
373
374 if window.position != cache.position
377 && let Some(position) = crate::winit_window_position(
378 &window.position,
379 &window.resolution,
380 &monitors,
381 winit_window.primary_monitor(),
382 winit_window.current_monitor(),
383 ) {
384 let should_set = match winit_window.outer_position() {
385 Ok(current_position) => current_position != position,
386 _ => true,
387 };
388
389 if should_set {
390 winit_window.set_outer_position(position);
391 }
392 }
393
394 if window.resolution != cache.resolution {
395 let cache_physical_size = PhysicalSize::new(
396 cache.resolution.physical_width(),
397 cache.resolution.physical_height(),
398 );
399 let requested_physical_size = PhysicalSize::new(
400 window.resolution.physical_width(),
401 window.resolution.physical_height(),
402 );
403
404 if cache_physical_size != requested_physical_size {
405 if let Some(new_physical_size) = winit_window.request_inner_size(requested_physical_size) {
407 let event = react_to_resize(entity, &mut window, new_physical_size);
408 window_resized.write(event.clone());
410 window_event.write(event.into());
411 }
412 }
413
414 let cache_scale_factor = cache.scale_factor();
415 let requested_scale_factor = window.scale_factor();
416
417 if cache_scale_factor != requested_scale_factor {
418 let event = WindowScaleFactorChanged { scale_factor: requested_scale_factor as f64, window: entity};
420 window_rescaled.write(event.clone());
422 window_event.write(event.into());
423 }
424 }
425
426 if window.physical_cursor_position() != cache.physical_cursor_position()
427 && let Some(physical_position) = window.physical_cursor_position() {
428 let position = PhysicalPosition::new(physical_position.x, physical_position.y);
429
430 if let Err(err) = winit_window.set_cursor_position(position) {
431 error!("could not set cursor position: {}", err);
432 }
433 }
434
435 if window.decorations != cache.decorations
436 && window.decorations != winit_window.is_decorated()
437 {
438 winit_window.set_decorations(window.decorations);
439 }
440
441 if window.resizable != cache.resizable
442 && window.resizable != winit_window.is_resizable()
443 {
444 winit_window.set_resizable(window.resizable);
445 }
446
447 if window.enabled_buttons != cache.enabled_buttons {
448 winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons));
449 }
450
451 if window.resize_constraints != cache.resize_constraints {
452 let constraints = window.resize_constraints.check_constraints();
453 let min_inner_size = LogicalSize {
454 width: constraints.min_width,
455 height: constraints.min_height,
456 };
457 let max_inner_size = LogicalSize {
458 width: constraints.max_width,
459 height: constraints.max_height,
460 };
461
462 winit_window.set_min_inner_size(Some(min_inner_size));
463 winit_window.set_max_inner_size(
464 if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
465 Some(max_inner_size)
466 } else {
467 None
468 },
469 );
470 }
471
472 if let Some(monitor_link) = monitor_relationship {
473 if let Some(winit_monitor) = winit_window.current_monitor() {
474 if let Some(linked_monitor) = monitors.find_entity(monitor_link.0) &&
475 winit_monitor != linked_monitor &&
476 let Some((_, winit_monitor_entity)) = monitors.monitors.iter().find(|(h, _)| h == &winit_monitor) {
477 commands.entity(entity).insert(OnMonitor(winit_monitor_entity.to_owned()));
478 }
479 } else {
480 commands.entity(entity).remove::<OnMonitor>();
481 }
482 } else {
483 if let Some(winit_monitor) = winit_window.current_monitor()
484 && let Some((_, winit_monitor_entity)) = monitors.monitors.iter()
485 .find(|(h, _)| h == &winit_monitor) {
486 commands.entity(entity).insert(OnMonitor(winit_monitor_entity.to_owned()));
487 }
488 }
489
490 if let Some(maximized) = window.internal.take_maximize_request() {
491 winit_window.set_maximized(maximized);
492 }
493
494 if let Some(minimized) = window.internal.take_minimize_request() {
495 winit_window.set_minimized(minimized);
496 }
497
498 if window.internal.take_move_request()
499 && let Err(e) = winit_window.drag_window() {
500 warn!("Winit returned an error while attempting to drag the window: {e}");
501 }
502
503 if let Some(resize_direction) = window.internal.take_resize_request()
504 && let Err(e) =
505 winit_window.drag_resize_window(convert_resize_direction(resize_direction))
506 {
507 warn!("Winit returned an error while attempting to drag resize the window: {e}");
508 }
509
510 if window.focused != cache.focused && window.focused {
511 winit_window.focus_window();
512 }
513
514 if window.window_level != cache.window_level {
515 winit_window.set_window_level(convert_window_level(window.window_level));
516 }
517
518 if window.transparent != cache.transparent {
520 window.transparent = cache.transparent;
521 warn!("Winit does not currently support updating transparency after window creation.");
522 }
523
524 #[cfg(target_arch = "wasm32")]
525 if window.canvas != cache.canvas {
526 window.canvas.clone_from(&cache.canvas);
527 warn!(
528 "Bevy currently doesn't support modifying the window canvas after initialization."
529 );
530 }
531
532 if window.ime_enabled != cache.ime_enabled {
533 winit_window.set_ime_allowed(window.ime_enabled);
534 }
535
536 if window.ime_position != cache.ime_position {
537 winit_window.set_ime_cursor_area(
538 LogicalPosition::new(window.ime_position.x, window.ime_position.y),
539 PhysicalSize::new(10, 10),
540 );
541 }
542
543 if window.window_theme != cache.window_theme {
544 winit_window.set_theme(window.window_theme.map(convert_window_theme));
545 }
546
547 if window.visible != cache.visible {
548 winit_window.set_visible(window.visible);
549 }
550
551 #[cfg(target_os = "ios")]
552 {
553 if window.recognize_pinch_gesture != cache.recognize_pinch_gesture {
554 winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture);
555 }
556 if window.recognize_rotation_gesture != cache.recognize_rotation_gesture {
557 winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture);
558 }
559 if window.recognize_doubletap_gesture != cache.recognize_doubletap_gesture {
560 winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture);
561 }
562 if window.recognize_pan_gesture != cache.recognize_pan_gesture {
563 match (
564 window.recognize_pan_gesture,
565 cache.recognize_pan_gesture,
566 ) {
567 (Some(_), Some(_)) => {
568 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");
569 }
570 (Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max),
571 _ => winit_window.recognize_pan_gesture(false, 0, 0),
572 }
573 }
574
575 if window.prefers_home_indicator_hidden != cache.prefers_home_indicator_hidden {
576 winit_window
577 .set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden);
578 }
579 if window.prefers_status_bar_hidden != cache.prefers_status_bar_hidden {
580 winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden);
581 }
582 if window.preferred_screen_edges_deferring_system_gestures
583 != cache
584 .preferred_screen_edges_deferring_system_gestures
585 {
586 use crate::converters::convert_screen_edge;
587 let preferred_edge =
588 convert_screen_edge(window.preferred_screen_edges_deferring_system_gestures);
589 winit_window.set_preferred_screen_edges_deferring_system_gestures(preferred_edge);
590 }
591 }
592 **cache = window.clone();
593 }
594 });
595}
596
597pub(crate) fn changed_cursor_options(
598 mut changed_windows: Query<
599 (
600 Entity,
601 &Window,
602 &mut CursorOptions,
603 &mut CachedCursorOptions,
604 ),
605 Changed<CursorOptions>,
606 >,
607 _non_send_marker: NonSendMarker,
608) {
609 WINIT_WINDOWS.with_borrow(|winit_windows| {
610 for (entity, window, mut cursor_options, mut cache) in &mut changed_windows {
611 let cursor_options = cursor_options.bypass_change_detection();
613 let Some(winit_window) = winit_windows.get_window(entity) else {
614 continue;
615 };
616 if let Err(err) =
618 crate::winit_windows::attempt_grab(winit_window, cursor_options.grab_mode)
619 {
620 warn!(
621 "Could not set cursor grab mode for window {}: {}",
622 window.title, err
623 );
624 cursor_options.grab_mode = cache.grab_mode;
625 } else {
626 cache.grab_mode = cursor_options.grab_mode;
627 }
628
629 if cursor_options.visible != cache.visible {
630 winit_window.set_cursor_visible(cursor_options.visible);
631 cache.visible = cursor_options.visible;
632 }
633
634 if cursor_options.hit_test != cache.hit_test {
635 if let Err(err) = winit_window.set_cursor_hittest(cursor_options.hit_test) {
636 warn!(
637 "Could not set cursor hit test for window {}: {}",
638 window.title, err
639 );
640 cursor_options.hit_test = cache.hit_test;
641 } else {
642 cache.hit_test = cursor_options.hit_test;
643 }
644 }
645 }
646 });
647}
648
649#[derive(Default, Component)]
652pub struct WinitWindowPressedKeys(pub(crate) HashMap<KeyCode, Key>);