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