1#![allow(
2 clippy::needless_question_mark,
3 clippy::too_many_arguments,
4 clippy::type_complexity,
5 clippy::module_inception,
6 clippy::single_match,
7 clippy::match_like_matches_macro
8)]
9
10mod config;
11mod converters;
12mod system;
13mod vulkano_windows;
14
15use bevy::{
16 app::{App, AppExit, Plugin},
17 ecs::{
18 event::{Events, ManualEventReader},
19 system::{SystemParam, SystemState},
20 },
21 input::{
22 keyboard::KeyboardInput,
23 mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
24 touch::TouchInput,
25 },
26 math::{ivec2, DVec2, Vec2},
27 prelude::*,
28 utils::Instant,
29 window::{
30 exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop,
31 ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested,
32 WindowCreated, WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged,
33 },
34};
35pub use config::*;
36#[cfg(feature = "gui")]
37pub use egui_winit_vulkano;
38use vulkano_util::context::{VulkanoConfig, VulkanoContext};
39pub use vulkano_windows::*;
40
41#[derive(Resource)]
43pub struct BevyVulkanoContext {
44 pub context: VulkanoContext,
45}
46
47#[cfg(target_os = "android")]
48pub use winit::platform::android::activity::AndroidApp;
49use winit::{
50 event::{self, DeviceEvent, Event, StartCause, WindowEvent},
51 event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget},
52};
53
54use crate::system::{changed_window, create_window, despawn_window, CachedWindow};
55
56#[cfg(target_os = "android")]
57pub static ANDROID_APP: once_cell::sync::OnceCell<AndroidApp> = once_cell::sync::OnceCell::new();
58
59#[derive(Default)]
63pub struct VulkanoWinitPlugin;
64
65impl Plugin for VulkanoWinitPlugin {
66 fn build(&self, app: &mut App) {
67 let mut event_loop_builder = EventLoopBuilder::<()>::with_user_event();
68
69 #[cfg(target_os = "android")]
70 {
71 use winit::platform::android::EventLoopBuilderExtAndroid;
72 event_loop_builder.with_android_app(
73 ANDROID_APP
74 .get()
75 .expect("Bevy must be setup with the #[bevy_main] macro on Android")
76 .clone(),
77 );
78 }
79
80 let event_loop = event_loop_builder.build();
81 app.insert_non_send_resource(event_loop);
82
83 let config = if app
85 .world
86 .get_non_send_resource::<BevyVulkanoSettings>()
87 .is_none()
88 {
89 BevyVulkanoSettings::default()
90 } else {
91 app.world
92 .remove_non_send_resource::<BevyVulkanoSettings>()
93 .unwrap()
94 };
95
96 let BevyVulkanoSettings {
98 vulkano_config, ..
99 } = config;
100 let vulkano_context = BevyVulkanoContext {
101 context: VulkanoContext::new(vulkano_config),
102 };
103 let new_config = BevyVulkanoSettings {
105 vulkano_config: VulkanoConfig::default(),
106 ..config
107 };
108
109 app.init_non_send_resource::<BevyVulkanoWindows>()
110 .insert_resource(vulkano_context)
111 .insert_non_send_resource(new_config)
112 .set_runner(winit_runner)
113 .add_systems(
116 Last,
117 (
118 changed_window.ambiguous_with(exit_on_all_closed),
119 despawn_window.after(changed_window),
121 ),
122 );
123
124 #[cfg(feature = "gui")]
125 {
126 app.add_systems(PreUpdate, begin_egui_frame_system);
127 }
128
129 let mut create_window_system_state: SystemState<(
130 Commands,
131 NonSendMut<EventLoop<()>>,
132 Query<(Entity, &mut Window)>,
133 EventWriter<WindowCreated>,
134 NonSendMut<BevyVulkanoWindows>,
135 Res<BevyVulkanoContext>,
136 NonSend<BevyVulkanoSettings>,
137 )> = SystemState::from_world(&mut app.world);
138
139 #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
142 {
143 let (
144 commands,
145 event_loop,
146 mut new_windows,
147 event_writer,
148 vulkano_windows,
149 context,
150 settings,
151 ) = create_window_system_state.get_mut(&mut app.world);
152
153 create_window(
157 commands,
158 &event_loop,
159 new_windows.iter_mut(),
160 event_writer,
161 vulkano_windows,
162 context,
163 settings,
164 );
165 }
166
167 create_window_system_state.apply(&mut app.world);
168 }
169}
170
171fn run<F>(event_loop: EventLoop<()>, event_handler: F) -> !
172where
173 F: 'static + FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
174{
175 event_loop.run(event_handler)
176}
177
178#[cfg(any(
179 target_os = "windows",
180 target_os = "macos",
181 target_os = "linux",
182 target_os = "dragonfly",
183 target_os = "freebsd",
184 target_os = "netbsd",
185 target_os = "openbsd"
186))]
187fn run_return<F>(event_loop: &mut EventLoop<()>, event_handler: F)
188where
189 F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
190{
191 use winit::platform::run_return::EventLoopExtRunReturn;
192 event_loop.run_return(event_handler);
193}
194
195#[cfg(not(any(
196 target_os = "windows",
197 target_os = "macos",
198 target_os = "linux",
199 target_os = "dragonfly",
200 target_os = "freebsd",
201 target_os = "netbsd",
202 target_os = "openbsd"
203)))]
204fn run_return<F>(_event_loop: &mut EventLoop<()>, _event_handler: F)
205where
206 F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
207{
208 panic!("Run return is not supported on this platform!")
209}
210
211#[derive(SystemParam)]
212struct WindowEvents<'w> {
213 window_resized: EventWriter<'w, WindowResized>,
214 window_close_requested: EventWriter<'w, WindowCloseRequested>,
215 window_scale_factor_changed: EventWriter<'w, WindowScaleFactorChanged>,
216 window_backend_scale_factor_changed: EventWriter<'w, WindowBackendScaleFactorChanged>,
217 window_focused: EventWriter<'w, WindowFocused>,
218 window_moved: EventWriter<'w, WindowMoved>,
219}
220
221#[derive(SystemParam)]
222struct InputEvents<'w> {
223 keyboard_input: EventWriter<'w, KeyboardInput>,
224 character_input: EventWriter<'w, ReceivedCharacter>,
225 mouse_button_input: EventWriter<'w, MouseButtonInput>,
226 mouse_wheel_input: EventWriter<'w, MouseWheel>,
227 touch_input: EventWriter<'w, TouchInput>,
228 ime_input: EventWriter<'w, Ime>,
229}
230
231#[derive(SystemParam)]
232struct CursorEvents<'w> {
233 cursor_moved: EventWriter<'w, CursorMoved>,
234 cursor_entered: EventWriter<'w, CursorEntered>,
235 cursor_left: EventWriter<'w, CursorLeft>,
236}
237
238struct WinitPersistentState {
240 active: bool,
242 low_power_event: bool,
245 redraw_request_sent: bool,
247 timeout_reached: bool,
249 last_update: Instant,
250}
251
252impl Default for WinitPersistentState {
253 fn default() -> Self {
254 Self {
255 active: false,
256 low_power_event: false,
257 redraw_request_sent: false,
258 timeout_reached: false,
259 last_update: Instant::now(),
260 }
261 }
262}
263
264pub fn winit_runner(mut app: App) {
265 let mut event_loop = app
266 .world
267 .remove_non_send_resource::<EventLoop<()>>()
268 .unwrap();
269
270 let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
271 let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
272 let mut winit_state = WinitPersistentState::default();
273 app.world
274 .insert_non_send_resource(event_loop.create_proxy());
275
276 let return_from_run = app
277 .world
278 .non_send_resource::<BevyVulkanoSettings>()
279 .return_from_run;
280
281 trace!("Entering winit event loop");
282
283 let mut focused_window_state: SystemState<(NonSend<BevyVulkanoSettings>, Query<&Window>)> =
284 SystemState::from_world(&mut app.world);
285
286 let mut create_window_system_state: SystemState<(
287 Commands,
288 Query<(Entity, &mut Window), Added<Window>>,
289 EventWriter<WindowCreated>,
290 NonSendMut<BevyVulkanoWindows>,
291 Res<BevyVulkanoContext>,
292 NonSend<BevyVulkanoSettings>,
293 )> = SystemState::from_world(&mut app.world);
294
295 let event_handler = move |event: Event<()>,
296 event_loop: &EventLoopWindowTarget<()>,
297 control_flow: &mut ControlFlow| {
298 #[cfg(feature = "trace")]
299 let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
300
301 if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
302 if app_exit_event_reader.read(app_exit_events).last().is_some() {
303 *control_flow = ControlFlow::Exit;
304 return;
305 }
306 }
307
308 match event {
309 event::Event::NewEvents(start) => {
310 let (config, window_focused_query) = focused_window_state.get(&app.world);
311
312 let app_focused = window_focused_query.iter().any(|window| window.focused);
313
314 let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. });
319 let now = Instant::now();
320 let manual_timeout_reached = match config.update_mode(app_focused) {
321 UpdateMode::Continuous => false,
322 UpdateMode::Reactive {
323 max_wait,
324 }
325 | UpdateMode::ReactiveLowPower {
326 max_wait,
327 } => now.duration_since(winit_state.last_update) >= *max_wait,
328 };
329 winit_state.low_power_event = false;
331 winit_state.timeout_reached = auto_timeout_reached || manual_timeout_reached;
332 }
333 #[allow(unused_mut)]
334 event::Event::WindowEvent {
335 event,
336 window_id: winit_window_id,
337 ..
338 } => {
339 let mut system_state: SystemState<(
341 NonSendMut<BevyVulkanoWindows>,
342 Query<(&mut Window, &mut CachedWindow)>,
343 WindowEvents,
344 InputEvents,
345 CursorEvents,
346 EventWriter<FileDragAndDrop>,
347 )> = SystemState::new(&mut app.world);
348 let (
349 mut vulkano_windows,
350 mut window_query,
351 mut window_events,
352 mut input_events,
353 mut cursor_events,
354 mut file_drag_and_drop_events,
355 ) = system_state.get_mut(&mut app.world);
356
357 let window_entity =
359 if let Some(entity) = vulkano_windows.get_window_entity(winit_window_id) {
360 entity
361 } else {
362 warn!(
363 "Skipped event {:?} for unknown winit Window Id {:?}",
364 event, winit_window_id
365 );
366 return;
367 };
368
369 let (mut window, mut cache) =
370 if let Ok((window, info)) = window_query.get_mut(window_entity) {
371 (window, info)
372 } else {
373 warn!(
374 "Window {:?} is missing `Window` component, skipping event {:?}",
375 window_entity, event
376 );
377 return;
378 };
379
380 #[cfg(feature = "gui")]
382 {
383 if let Some(vulkano_window) =
384 vulkano_windows.get_vulkano_window_mut(window_entity)
385 {
386 if vulkano_window.gui.update(&event) {
388 return;
389 }
390 }
391 }
392
393 winit_state.low_power_event = true;
394
395 match event {
396 WindowEvent::Resized(size) => {
397 window
398 .resolution
399 .set_physical_resolution(size.width, size.height);
400
401 window_events.window_resized.send(WindowResized {
402 window: window_entity,
403 width: window.width(),
404 height: window.height(),
405 });
406 }
407 WindowEvent::CloseRequested => {
408 window_events
409 .window_close_requested
410 .send(WindowCloseRequested {
411 window: window_entity,
412 });
413 }
414 WindowEvent::KeyboardInput {
415 ref input, ..
416 } => {
417 input_events
418 .keyboard_input
419 .send(converters::convert_keyboard_input(input, window_entity));
420 }
421 WindowEvent::CursorMoved {
422 position, ..
423 } => {
424 let physical_position = DVec2::new(position.x, position.y);
425
426 window.set_physical_cursor_position(Some(physical_position));
427
428 cursor_events.cursor_moved.send(CursorMoved {
429 window: window_entity,
430 position: (physical_position / window.resolution.scale_factor())
431 .as_vec2(),
432 });
433 }
434 WindowEvent::CursorEntered {
435 ..
436 } => {
437 cursor_events.cursor_entered.send(CursorEntered {
438 window: window_entity,
439 });
440 }
441 WindowEvent::CursorLeft {
442 ..
443 } => {
444 window.set_physical_cursor_position(None);
445
446 cursor_events.cursor_left.send(CursorLeft {
447 window: window_entity,
448 });
449 }
450 WindowEvent::MouseInput {
451 state,
452 button,
453 ..
454 } => {
455 input_events.mouse_button_input.send(MouseButtonInput {
456 button: converters::convert_mouse_button(button),
457 state: converters::convert_element_state(state),
458 window: window_entity,
459 });
460 }
461 WindowEvent::MouseWheel {
462 delta, ..
463 } => match delta {
464 event::MouseScrollDelta::LineDelta(x, y) => {
465 input_events.mouse_wheel_input.send(MouseWheel {
466 unit: MouseScrollUnit::Line,
467 x,
468 y,
469 window: window_entity,
470 });
471 }
472 event::MouseScrollDelta::PixelDelta(p) => {
473 input_events.mouse_wheel_input.send(MouseWheel {
474 unit: MouseScrollUnit::Pixel,
475 x: p.x as f32,
476 y: p.y as f32,
477 window: window_entity,
478 });
479 }
480 },
481 WindowEvent::Touch(touch) => {
482 let location = touch.location.to_logical(window.resolution.scale_factor());
483
484 input_events
486 .touch_input
487 .send(converters::convert_touch_input(touch, location));
488 }
489 WindowEvent::ReceivedCharacter(c) => {
490 input_events.character_input.send(ReceivedCharacter {
491 window: window_entity,
492 char: c,
493 });
494 }
495 WindowEvent::ScaleFactorChanged {
496 scale_factor,
497 new_inner_size,
498 } => {
499 window_events.window_backend_scale_factor_changed.send(
500 WindowBackendScaleFactorChanged {
501 window: window_entity,
502 scale_factor,
503 },
504 );
505
506 let prior_factor = window.resolution.scale_factor();
507 window.resolution.set_scale_factor(scale_factor);
508 let new_factor = window.resolution.scale_factor();
509
510 if let Some(forced_factor) = window.resolution.scale_factor_override() {
511 *new_inner_size =
516 winit::dpi::LogicalSize::new(window.width(), window.height())
517 .to_physical::<u32>(forced_factor);
518 } else if approx::relative_ne!(new_factor, prior_factor) {
520 window_events.window_scale_factor_changed.send(
522 WindowScaleFactorChanged {
523 window: window_entity,
524 scale_factor,
525 },
526 );
527 }
528
529 let new_logical_width = (new_inner_size.width as f64 / new_factor) as f32;
530 let new_logical_height = (new_inner_size.height as f64 / new_factor) as f32;
531 if approx::relative_ne!(window.width(), new_logical_width)
532 || approx::relative_ne!(window.height(), new_logical_height)
533 {
534 window_events.window_resized.send(WindowResized {
535 window: window_entity,
536 width: new_logical_width,
537 height: new_logical_height,
538 });
539 }
540 window
541 .resolution
542 .set_physical_resolution(new_inner_size.width, new_inner_size.height);
543 }
544 WindowEvent::Focused(focused) => {
545 window.focused = focused;
547
548 window_events.window_focused.send(WindowFocused {
549 window: window_entity,
550 focused,
551 });
552 }
553 WindowEvent::DroppedFile(path_buf) => {
554 file_drag_and_drop_events.send(FileDragAndDrop::DroppedFile {
555 window: window_entity,
556 path_buf,
557 });
558 }
559 WindowEvent::HoveredFile(path_buf) => {
560 file_drag_and_drop_events.send(FileDragAndDrop::HoveredFile {
561 window: window_entity,
562 path_buf,
563 });
564 }
565 WindowEvent::HoveredFileCancelled => {
566 file_drag_and_drop_events.send(FileDragAndDrop::HoveredFileCanceled {
567 window: window_entity,
568 });
569 }
570 WindowEvent::Moved(position) => {
571 let position = ivec2(position.x, position.y);
572
573 window.position.set(position);
574
575 window_events.window_moved.send(WindowMoved {
576 entity: window_entity,
577 position,
578 });
579 }
580 WindowEvent::Ime(event) => match event {
581 event::Ime::Preedit(value, cursor) => {
582 input_events.ime_input.send(Ime::Preedit {
583 window: window_entity,
584 value,
585 cursor,
586 });
587 }
588 event::Ime::Commit(value) => input_events.ime_input.send(Ime::Commit {
589 window: window_entity,
590 value,
591 }),
592 event::Ime::Enabled => input_events.ime_input.send(Ime::Enabled {
593 window: window_entity,
594 }),
595 event::Ime::Disabled => input_events.ime_input.send(Ime::Disabled {
596 window: window_entity,
597 }),
598 },
599 _ => {}
600 }
601
602 if window.is_changed() {
603 cache.window = window.clone();
604 }
605 }
606 event::Event::DeviceEvent {
607 event:
608 DeviceEvent::MouseMotion {
609 delta: (x, y),
610 },
611 ..
612 } => {
613 let mut system_state: SystemState<EventWriter<MouseMotion>> =
614 SystemState::new(&mut app.world);
615 let mut mouse_motion = system_state.get_mut(&mut app.world);
616
617 mouse_motion.send(MouseMotion {
618 delta: Vec2::new(x as f32, y as f32),
619 });
620 }
621 event::Event::Suspended => {
622 winit_state.active = false;
623 #[cfg(target_os = "android")]
624 {
625 *control_flow = ControlFlow::Exit;
629 }
630 }
631 event::Event::Resumed => {
632 winit_state.active = true;
633 }
634 event::Event::MainEventsCleared => {
635 let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
636
637 let update = if winit_state.active {
638 let app_focused = window_focused_query.iter().any(|window| window.focused);
640 match winit_config.update_mode(app_focused) {
641 UpdateMode::Continuous
642 | UpdateMode::Reactive {
643 ..
644 } => true,
645 UpdateMode::ReactiveLowPower {
646 ..
647 } => {
648 winit_state.low_power_event
649 || winit_state.redraw_request_sent
650 || winit_state.timeout_reached
651 }
652 }
653 } else {
654 false
655 };
656
657 if update {
658 winit_state.last_update = Instant::now();
659 app.update();
660 }
661 }
662 Event::RedrawEventsCleared => {
663 {
664 let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
666
667 let app_focused = window_focused_query.iter().any(|window| window.focused);
669
670 let now = Instant::now();
671 use UpdateMode::*;
672 *control_flow = match winit_config.update_mode(app_focused) {
673 Continuous => ControlFlow::Poll,
674 Reactive {
675 max_wait,
676 }
677 | ReactiveLowPower {
678 max_wait,
679 } => {
680 if let Some(instant) = now.checked_add(*max_wait) {
681 ControlFlow::WaitUntil(instant)
682 } else {
683 ControlFlow::Wait
684 }
685 }
686 };
687 }
688
689 let mut redraw = false;
693 if let Some(app_redraw_events) = app.world.get_resource::<Events<RequestRedraw>>() {
694 if redraw_event_reader.read(app_redraw_events).last().is_some() {
695 *control_flow = ControlFlow::Poll;
696 redraw = true;
697 }
698 }
699
700 winit_state.redraw_request_sent = redraw;
701 }
702
703 _ => (),
704 }
705
706 if winit_state.active {
707 let (
708 commands,
709 mut new_windows,
710 created_window_writer,
711 vulkano_windows,
712 context,
713 settings,
714 ) = create_window_system_state.get_mut(&mut app.world);
715
716 create_window(
718 commands,
719 event_loop,
720 new_windows.iter_mut(),
721 created_window_writer,
722 vulkano_windows,
723 context,
724 settings,
725 );
726
727 create_window_system_state.apply(&mut app.world);
728 }
729 };
730
731 if return_from_run {
733 run_return(&mut event_loop, event_handler);
734 } else {
735 run(event_loop, event_handler);
736 }
737}
738
739#[cfg(feature = "gui")]
740pub fn begin_egui_frame_system(mut vulkano_windows: NonSendMut<BevyVulkanoWindows>) {
741 for (_, w) in vulkano_windows.windows.iter_mut() {
742 w.gui.begin_frame();
743 }
744}