goud_engine/sdk/input.rs
1//! # SDK Input API
2//!
3//! Provides methods on [`GoudGame`](super::GoudGame) for querying input state:
4//! keyboard, mouse, and action mapping.
5//!
6//! The input system is built on the [`InputManager`] ECS resource. The SDK
7//! wraps it so game code can query input without reaching into the ECS layer.
8//!
9//! # Availability
10//!
11//! This module requires the `native` feature (desktop platform with GLFW).
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! use goud_engine::sdk::GoudGame;
17//! use glfw::Key;
18//!
19//! let mut game = GoudGame::new(Default::default()).unwrap();
20//!
21//! // In your game loop:
22//! if game.is_key_pressed(Key::W) {
23//! // Move forward
24//! }
25//! if game.is_key_just_pressed(Key::Space) {
26//! // Jump (once per press)
27//! }
28//!
29//! let (mx, my) = game.mouse_position();
30//! let scroll = game.scroll_delta();
31//! ```
32
33use super::GoudGame;
34use crate::ecs::InputManager;
35
36// Re-export input types for SDK users so callers do not need to depend
37// on `glfw` or reach into `ecs::` directly.
38pub use crate::ecs::InputBinding;
39pub use glfw::{Key, MouseButton};
40
41// =============================================================================
42// Input API (annotated for FFI generation)
43// =============================================================================
44
45// NOTE: FFI wrappers are hand-written in ffi/input.rs. The `#[goud_api]`
46// attribute is omitted here to avoid duplicate `#[no_mangle]` symbol conflicts.
47impl GoudGame {
48 // =========================================================================
49 // Keyboard Input
50 // =========================================================================
51
52 /// Returns `true` if the given key is currently held down.
53 ///
54 /// # Example
55 ///
56 /// ```rust,ignore
57 /// if game.is_key_pressed(Key::W) {
58 /// // Move forward continuously while held
59 /// }
60 /// ```
61 #[inline]
62 pub fn is_key_pressed(&self, key: Key) -> bool {
63 self.input_manager.key_pressed(key)
64 }
65
66 /// Returns `true` if the given key was pressed this frame (not held from previous frame).
67 ///
68 /// Use this for one-shot actions like jumping or menu selection.
69 #[inline]
70 pub fn is_key_just_pressed(&self, key: Key) -> bool {
71 self.input_manager.key_just_pressed(key)
72 }
73
74 /// Returns `true` if the given key was released this frame.
75 #[inline]
76 pub fn is_key_just_released(&self, key: Key) -> bool {
77 self.input_manager.key_just_released(key)
78 }
79
80 // =========================================================================
81 // Mouse Input
82 // =========================================================================
83
84 /// Returns `true` if the given mouse button is currently held down.
85 #[inline]
86 pub fn is_mouse_button_pressed(&self, button: MouseButton) -> bool {
87 self.input_manager.mouse_button_pressed(button)
88 }
89
90 /// Returns `true` if the given mouse button was pressed this frame.
91 #[inline]
92 pub fn is_mouse_button_just_pressed(&self, button: MouseButton) -> bool {
93 self.input_manager.mouse_button_just_pressed(button)
94 }
95
96 /// Returns `true` if the given mouse button was released this frame.
97 #[inline]
98 pub fn is_mouse_button_just_released(&self, button: MouseButton) -> bool {
99 self.input_manager.mouse_button_just_released(button)
100 }
101
102 /// Returns the current mouse position in window coordinates.
103 ///
104 /// Returns `(x, y)` where `(0, 0)` is the top-left corner.
105 #[inline]
106 pub fn mouse_position(&self) -> (f32, f32) {
107 let pos = self.input_manager.mouse_position();
108 (pos.x, pos.y)
109 }
110
111 /// Returns the mouse movement delta since the last frame.
112 ///
113 /// Returns `(dx, dy)` where positive X is right and positive Y is down.
114 #[inline]
115 pub fn mouse_delta(&self) -> (f32, f32) {
116 let delta = self.input_manager.mouse_delta();
117 (delta.x, delta.y)
118 }
119
120 /// Returns the scroll wheel delta since the last frame.
121 ///
122 /// Returns `(horizontal, vertical)` scroll amounts.
123 #[inline]
124 pub fn scroll_delta(&self) -> (f32, f32) {
125 let delta = self.input_manager.scroll_delta();
126 (delta.x, delta.y)
127 }
128
129 // =========================================================================
130 // Action Mapping
131 // =========================================================================
132
133 /// Maps a key to a named action.
134 ///
135 /// An action can have multiple key bindings. When any bound key is pressed,
136 /// the action is considered active.
137 ///
138 /// # Example
139 ///
140 /// ```rust,ignore
141 /// game.map_action_key("Jump", Key::Space);
142 /// game.map_action_key("Jump", Key::W);
143 ///
144 /// if game.is_action_pressed("Jump") {
145 /// // Triggered by Space OR W
146 /// }
147 /// ```
148 #[inline]
149 pub fn map_action_key(&mut self, action: &str, key: Key) {
150 self.input_manager
151 .map_action(action, InputBinding::Key(key));
152 }
153
154 /// Maps a mouse button to a named action.
155 #[inline]
156 pub fn map_action_mouse_button(&mut self, action: &str, button: MouseButton) {
157 self.input_manager
158 .map_action(action, InputBinding::MouseButton(button));
159 }
160
161 /// Returns `true` if any binding for the named action is currently held.
162 #[inline]
163 pub fn is_action_pressed(&self, action: &str) -> bool {
164 self.input_manager.action_pressed(action)
165 }
166
167 /// Returns `true` if any binding for the named action was pressed this frame.
168 #[inline]
169 pub fn is_action_just_pressed(&self, action: &str) -> bool {
170 self.input_manager.action_just_pressed(action)
171 }
172
173 /// Returns `true` if any binding for the named action was released this frame.
174 #[inline]
175 pub fn is_action_just_released(&self, action: &str) -> bool {
176 self.input_manager.action_just_released(action)
177 }
178
179 // =========================================================================
180 // Direct InputManager Access
181 // =========================================================================
182
183 /// Returns a reference to the underlying [`InputManager`].
184 ///
185 /// Use this for advanced input queries (gamepad, input buffering, etc.)
186 /// that are not exposed through convenience methods.
187 #[inline]
188 pub fn input(&self) -> &InputManager {
189 &self.input_manager
190 }
191
192 /// Returns a mutable reference to the underlying [`InputManager`].
193 ///
194 /// Use this for advanced configuration (deadzone, buffer duration, etc.).
195 #[inline]
196 pub fn input_mut(&mut self) -> &mut InputManager {
197 &mut self.input_manager
198 }
199}
200
201// =============================================================================
202// Tests
203// =============================================================================
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::sdk::GameConfig;
209
210 #[test]
211 fn test_input_manager_accessible() {
212 let game = GoudGame::new(GameConfig::default()).unwrap();
213 // InputManager should be initialized and accessible
214 let _input = game.input();
215 }
216
217 #[test]
218 fn test_input_manager_mutable() {
219 let mut game = GoudGame::new(GameConfig::default()).unwrap();
220 let input = game.input_mut();
221 // Should be able to configure the input manager
222 input.set_analog_deadzone(0.2);
223 assert!((input.analog_deadzone() - 0.2).abs() < 0.001);
224 }
225
226 #[test]
227 fn test_key_not_pressed_by_default() {
228 let game = GoudGame::new(GameConfig::default()).unwrap();
229 assert!(!game.is_key_pressed(Key::Space));
230 assert!(!game.is_key_just_pressed(Key::Space));
231 assert!(!game.is_key_just_released(Key::Space));
232 }
233
234 #[test]
235 fn test_mouse_button_not_pressed_by_default() {
236 let game = GoudGame::new(GameConfig::default()).unwrap();
237 assert!(!game.is_mouse_button_pressed(MouseButton::Button1));
238 assert!(!game.is_mouse_button_just_pressed(MouseButton::Button1));
239 }
240
241 #[test]
242 fn test_mouse_position_default() {
243 let game = GoudGame::new(GameConfig::default()).unwrap();
244 let (x, y) = game.mouse_position();
245 assert!((x - 0.0).abs() < 0.001);
246 assert!((y - 0.0).abs() < 0.001);
247 }
248
249 #[test]
250 fn test_action_mapping() {
251 let mut game = GoudGame::new(GameConfig::default()).unwrap();
252 game.map_action_key("Jump", Key::Space);
253 // Action exists but key is not pressed
254 assert!(!game.is_action_pressed("Jump"));
255 }
256
257 #[test]
258 fn test_key_press_and_query() {
259 let mut game = GoudGame::new(GameConfig::default()).unwrap();
260 // Simulate a key press through the input manager
261 game.input_mut().press_key(Key::W);
262 assert!(game.is_key_pressed(Key::W));
263 }
264
265 #[test]
266 fn test_scroll_delta_default() {
267 let game = GoudGame::new(GameConfig::default()).unwrap();
268 let (sx, sy) = game.scroll_delta();
269 assert!((sx - 0.0).abs() < 0.001);
270 assert!((sy - 0.0).abs() < 0.001);
271 }
272
273 #[test]
274 fn test_mouse_delta_default() {
275 let game = GoudGame::new(GameConfig::default()).unwrap();
276 let (dx, dy) = game.mouse_delta();
277 assert!((dx - 0.0).abs() < 0.001);
278 assert!((dy - 0.0).abs() < 0.001);
279 }
280}