Skip to main content

godot_bevy/plugins/input/
events.rs

1use bevy_app::{App, First, Plugin};
2use bevy_ecs::{
3    message::{Message, MessageWriter, message_update_system},
4    schedule::IntoScheduleConfigs,
5    system::NonSendMut,
6};
7use bevy_math::Vec2;
8use bevy_reflect::Reflect;
9use godot::{
10    classes::{
11        InputEvent as GodotInputEvent, InputEventJoypadButton, InputEventJoypadMotion,
12        InputEventKey, InputEventMouseButton, InputEventMouseMotion, InputEventPanGesture,
13        InputEventScreenTouch, InputMap,
14    },
15    global::Key,
16    obj::{EngineEnum, Gd, Singleton},
17};
18use tracing::trace;
19
20/// Plugin that handles Godot input events and converts them to Bevy messages.
21/// This is the base input plugin that provides raw input message types.
22///
23/// For higher-level input handling, consider using:
24/// - `BevyInputBridgePlugin` for Bevy's standard input resources
25/// - Custom input handling systems that read these messages
26#[derive(Default)]
27pub struct GodotInputEventPlugin;
28
29/// Alias for backwards compatibility
30#[deprecated(note = "Use GodotInputEventPlugin instead")]
31pub type GodotInputPlugin = GodotInputEventPlugin;
32
33impl Plugin for GodotInputEventPlugin {
34    fn build(&self, app: &mut App) {
35        app.add_systems(First, write_input_messages.before(message_update_system))
36            .add_message::<KeyboardInput>()
37            .add_message::<MouseButtonInput>()
38            .add_message::<MouseMotion>()
39            .add_message::<TouchInput>()
40            .add_message::<ActionInput>()
41            .add_message::<GamepadButtonInput>()
42            .add_message::<GamepadAxisInput>()
43            .add_message::<PanGestureInput>();
44    }
45}
46
47/// Keyboard key press/release event
48#[derive(Debug, Message, Clone)]
49pub struct KeyboardInput {
50    pub keycode: Key,
51    pub physical_keycode: Option<Key>,
52    pub pressed: bool,
53    pub echo: bool,
54}
55
56/// Mouse button press/release event
57#[derive(Debug, Message, Clone)]
58pub struct MouseButtonInput {
59    pub button: MouseButton,
60    pub pressed: bool,
61    pub position: Vec2,
62    pub factor: f32,
63    pub canceled: bool,
64    pub is_double_click: bool,
65}
66
67/// Mouse motion event
68#[derive(Debug, Message, Clone)]
69pub struct MouseMotion {
70    pub delta: Vec2,
71    pub position: Vec2,
72}
73
74/// Touch input event (for mobile/touchscreen)
75#[derive(Debug, Message, Clone)]
76pub struct TouchInput {
77    pub finger_id: i32,
78    pub position: Vec2,
79    pub pressed: bool,
80}
81
82/// Godot action input event (for input map actions)
83#[derive(Debug, Message, Clone)]
84pub struct ActionInput {
85    pub action: String,
86    pub pressed: bool,
87    pub strength: f32,
88}
89
90/// Gamepad button input event (from Godot InputEventJoypadButton)
91#[derive(Debug, Message, Clone)]
92pub struct GamepadButtonInput {
93    pub device: i32,
94    pub button_index: i32,
95    pub pressed: bool,
96    pub pressure: f32,
97}
98
99/// Gamepad axis input event (from Godot InputEventJoypadMotion)
100#[derive(Debug, Message, Clone)]
101pub struct GamepadAxisInput {
102    pub device: i32,
103    pub axis: i32,
104    pub value: f32,
105}
106
107/// Two-finger pan gesture input event (from Godot InputEventPanGesture)
108#[derive(Debug, Message, Clone)]
109pub struct PanGestureInput {
110    pub delta: Vec2,
111}
112
113/// Mouse button types
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
115pub enum MouseButton {
116    Left,
117    Right,
118    Middle,
119    WheelUp,
120    WheelDown,
121    WheelLeft,
122    WheelRight,
123    Extra1,
124    Extra2,
125}
126
127impl From<godot::global::MouseButton> for MouseButton {
128    fn from(button: godot::global::MouseButton) -> Self {
129        match button {
130            godot::global::MouseButton::LEFT => MouseButton::Left,
131            godot::global::MouseButton::RIGHT => MouseButton::Right,
132            godot::global::MouseButton::MIDDLE => MouseButton::Middle,
133            godot::global::MouseButton::WHEEL_UP => MouseButton::WheelUp,
134            godot::global::MouseButton::WHEEL_DOWN => MouseButton::WheelDown,
135            godot::global::MouseButton::WHEEL_LEFT => MouseButton::WheelLeft,
136            godot::global::MouseButton::WHEEL_RIGHT => MouseButton::WheelRight,
137            godot::global::MouseButton::XBUTTON1 => MouseButton::Extra1,
138            godot::global::MouseButton::XBUTTON2 => MouseButton::Extra2,
139            _ => MouseButton::Left, // fallback
140        }
141    }
142}
143
144#[allow(clippy::too_many_arguments)]
145fn write_input_messages(
146    events: NonSendMut<InputEventReader>,
147    mut keyboard_events: MessageWriter<KeyboardInput>,
148    mut mouse_button_events: MessageWriter<MouseButtonInput>,
149    mut mouse_motion_events: MessageWriter<MouseMotion>,
150    mut touch_events: MessageWriter<TouchInput>,
151    mut action_events: MessageWriter<ActionInput>,
152    mut gamepad_button_events: MessageWriter<GamepadButtonInput>,
153    mut gamepad_axis_events: MessageWriter<GamepadAxisInput>,
154    mut pan_gesture_events: MessageWriter<PanGestureInput>,
155) {
156    for (event_type, input_event) in events.0.try_iter() {
157        trace!("Processing {:?} input event", event_type);
158
159        match event_type {
160            InputEventType::Normal => {
161                check_action_events(&input_event, &mut action_events);
162                extract_mouse_motion_events(input_event, &mut mouse_motion_events);
163            }
164            InputEventType::Unhandled => {
165                extract_basic_input_events(
166                    input_event,
167                    &mut keyboard_events,
168                    &mut mouse_button_events,
169                    &mut touch_events,
170                    &mut gamepad_button_events,
171                    &mut gamepad_axis_events,
172                    &mut pan_gesture_events,
173                );
174            }
175        }
176    }
177}
178
179fn extract_mouse_motion_events(
180    input_event: Gd<GodotInputEvent>,
181    mouse_motion_events: &mut MessageWriter<MouseMotion>,
182) {
183    if let Ok(mouse_motion_event) = input_event.try_cast::<InputEventMouseMotion>() {
184        let position = mouse_motion_event.get_position();
185        let relative = mouse_motion_event.get_relative();
186        mouse_motion_events.write(MouseMotion {
187            delta: Vec2::new(relative.x, relative.y),
188            position: Vec2::new(position.x, position.y),
189        });
190    }
191}
192
193#[allow(clippy::too_many_arguments)]
194fn extract_basic_input_events(
195    input_event: Gd<GodotInputEvent>,
196    keyboard_events: &mut MessageWriter<KeyboardInput>,
197    mouse_button_events: &mut MessageWriter<MouseButtonInput>,
198    touch_events: &mut MessageWriter<TouchInput>,
199    gamepad_button_events: &mut MessageWriter<GamepadButtonInput>,
200    gamepad_axis_events: &mut MessageWriter<GamepadAxisInput>,
201    pan_gesture_events: &mut MessageWriter<PanGestureInput>,
202) {
203    // Keyboard input
204    let input_event = match input_event.try_cast::<InputEventKey>() {
205        Ok(key_event) => {
206            keyboard_events.write(KeyboardInput {
207                keycode: key_event.get_keycode(),
208                physical_keycode: Some(key_event.get_physical_keycode()),
209                pressed: key_event.is_pressed(),
210                echo: key_event.is_echo(),
211            });
212            return;
213        }
214        Err(original) => original,
215    };
216
217    // Mouse button input
218    let input_event = match input_event.try_cast::<InputEventMouseButton>() {
219        Ok(mouse_button_event) => {
220            let position = mouse_button_event.get_position();
221            mouse_button_events.write(MouseButtonInput {
222                button: mouse_button_event.get_button_index().into(),
223                pressed: mouse_button_event.is_pressed(),
224                position: Vec2::new(position.x, position.y),
225                factor: mouse_button_event.get_factor(),
226                canceled: mouse_button_event.is_canceled(),
227                is_double_click: mouse_button_event.is_double_click(),
228            });
229            return;
230        }
231        Err(original) => original,
232    };
233
234    // Touch input
235    let input_event = match input_event.try_cast::<InputEventScreenTouch>() {
236        Ok(touch_event) => {
237            let position = touch_event.get_position();
238            touch_events.write(TouchInput {
239                finger_id: touch_event.get_index(),
240                position: Vec2::new(position.x, position.y),
241                pressed: touch_event.is_pressed(),
242            });
243            return;
244        }
245        Err(original) => original,
246    };
247
248    // Gamepad button input
249    let input_event = match input_event.try_cast::<InputEventJoypadButton>() {
250        Ok(gamepad_button_event) => {
251            gamepad_button_events.write(GamepadButtonInput {
252                device: gamepad_button_event.get_device(),
253                button_index: gamepad_button_event.get_button_index().ord(),
254                pressed: gamepad_button_event.is_pressed(),
255                pressure: gamepad_button_event.get_pressure(),
256            });
257            return;
258        }
259        Err(original) => original,
260    };
261
262    // Gamepad axis input
263    let input_event = match input_event.try_cast::<InputEventJoypadMotion>() {
264        Ok(gamepad_motion_event) => {
265            gamepad_axis_events.write(GamepadAxisInput {
266                device: gamepad_motion_event.get_device(),
267                axis: gamepad_motion_event.get_axis().ord(),
268                value: gamepad_motion_event.get_axis_value(),
269            });
270            return;
271        }
272        Err(original) => original,
273    };
274
275    // Two-finger pan gesture
276    if let Ok(pan_gesture_event) = input_event.try_cast::<InputEventPanGesture>() {
277        let delta = pan_gesture_event.get_delta();
278        pan_gesture_events.write(PanGestureInput {
279            delta: Vec2::new(delta.x, delta.y),
280        });
281    }
282}
283
284fn check_action_events(
285    input_event: &Gd<GodotInputEvent>,
286    action_events: &mut MessageWriter<ActionInput>,
287) {
288    let mut input_map = InputMap::singleton();
289    let actions = input_map.get_actions();
290
291    for action_name in actions.iter_shared() {
292        if input_event.is_action(&action_name) {
293            let pressed = input_event.is_action_pressed(&action_name);
294            let strength = input_event.get_action_strength(&action_name);
295            let action_str = action_name.to_string();
296
297            trace!(
298                "Generated ActionInput: '{}' {} (strength: {:.2})",
299                action_str,
300                if pressed { "pressed" } else { "released" },
301                strength
302            );
303
304            action_events.write(ActionInput {
305                action: action_str,
306                pressed,
307                strength,
308            });
309        }
310    }
311}
312
313#[doc(hidden)]
314#[derive(Debug, PartialEq, Eq, Clone, Copy)]
315pub enum InputEventType {
316    Normal,
317    Unhandled,
318}
319
320#[doc(hidden)]
321pub struct InputEventReader(pub crossbeam_channel::Receiver<(InputEventType, Gd<GodotInputEvent>)>);