custom_user_event/
custom_user_event.rs

1//! Shows how to create a custom event that can be handled by `winit`'s event loop.
2
3use bevy::{
4    prelude::*,
5    winit::{EventLoopProxyWrapper, WakeUp, WinitPlugin},
6};
7use std::fmt::Formatter;
8
9#[derive(Default, Debug, Event)]
10enum CustomEvent {
11    #[default]
12    WakeUp,
13    Key(char),
14}
15
16impl std::fmt::Display for CustomEvent {
17    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Self::WakeUp => write!(f, "Wake up"),
20            Self::Key(ch) => write!(f, "Key: {ch}"),
21        }
22    }
23}
24
25fn main() {
26    let winit_plugin = WinitPlugin::<CustomEvent>::default();
27
28    App::new()
29        .add_plugins(
30            DefaultPlugins
31                .build()
32                // Only one event type can be handled at once
33                // so we must disable the default event type
34                .disable::<WinitPlugin<WakeUp>>()
35                .add(winit_plugin),
36        )
37        .add_systems(
38            Startup,
39            (
40                setup,
41                #[cfg(target_arch = "wasm32")]
42                wasm::setup_js_closure,
43            ),
44        )
45        .add_systems(Update, (send_event, handle_event))
46        .run();
47}
48
49fn setup(mut commands: Commands) {
50    commands.spawn(Camera2d);
51}
52
53fn send_event(
54    input: Res<ButtonInput<KeyCode>>,
55    event_loop_proxy: Res<EventLoopProxyWrapper<CustomEvent>>,
56) {
57    if input.just_pressed(KeyCode::Space) {
58        let _ = event_loop_proxy.send_event(CustomEvent::WakeUp);
59    }
60
61    // This simulates sending a custom event through an external thread.
62    #[cfg(not(target_arch = "wasm32"))]
63    if input.just_pressed(KeyCode::KeyE) {
64        let event_loop_proxy = event_loop_proxy.clone();
65        let handler = std::thread::spawn(move || {
66            let _ = event_loop_proxy.clone().send_event(CustomEvent::Key('e'));
67        });
68
69        handler.join().unwrap();
70    }
71}
72
73fn handle_event(mut events: EventReader<CustomEvent>) {
74    for evt in events.read() {
75        info!("Received event: {evt:?}");
76    }
77}
78
79/// Since the [`EventLoopProxy`] can be exposed to the javascript environment, it can
80/// be used to send events inside the loop, to be handled by a system or simply to wake up
81/// the loop if that's currently waiting for a timeout or a user event.
82#[cfg(target_arch = "wasm32")]
83pub(crate) mod wasm {
84    use super::*;
85    use bevy::winit::EventLoopProxy;
86    use wasm_bindgen::{prelude::*, JsCast};
87    use web_sys::KeyboardEvent;
88
89    pub(crate) fn setup_js_closure(event_loop: Res<EventLoopProxyWrapper<CustomEvent>>) {
90        let window = web_sys::window().unwrap();
91        let document = window.document().unwrap();
92
93        let event_loop = event_loop.clone();
94        let closure = Closure::wrap(Box::new(move |event: KeyboardEvent| {
95            let key = event.key();
96            if key == "e" {
97                send_custom_event('e', &event_loop).unwrap();
98            }
99        }) as Box<dyn FnMut(KeyboardEvent)>);
100
101        document
102            .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())
103            .unwrap();
104
105        closure.forget();
106    }
107
108    fn send_custom_event(ch: char, proxy: &EventLoopProxy<CustomEvent>) -> Result<(), String> {
109        proxy
110            .send_event(CustomEvent::Key(ch))
111            .map_err(|_| "Failed to send event".to_string())
112    }
113}