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#[derive(Default)]
27pub struct GodotInputEventPlugin;
28
29#[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#[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#[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#[derive(Debug, Message, Clone)]
69pub struct MouseMotion {
70 pub delta: Vec2,
71 pub position: Vec2,
72}
73
74#[derive(Debug, Message, Clone)]
76pub struct TouchInput {
77 pub finger_id: i32,
78 pub position: Vec2,
79 pub pressed: bool,
80}
81
82#[derive(Debug, Message, Clone)]
84pub struct ActionInput {
85 pub action: String,
86 pub pressed: bool,
87 pub strength: f32,
88}
89
90#[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#[derive(Debug, Message, Clone)]
101pub struct GamepadAxisInput {
102 pub device: i32,
103 pub axis: i32,
104 pub value: f32,
105}
106
107#[derive(Debug, Message, Clone)]
109pub struct PanGestureInput {
110 pub delta: Vec2,
111}
112
113#[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, }
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 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 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 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 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 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 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>)>);