Skip to main content

goud_engine/core/input_manager/
gamepad.rs

1//! Gamepad input methods: buttons, analog axes, connection status, and vibration.
2
3#[cfg(feature = "native")]
4use glfw::GamepadAxis;
5
6use crate::core::math::Vec2;
7
8use super::manager::InputManager;
9use super::types::{GamepadState, InputBinding};
10
11impl InputManager {
12    // === Gamepad Input ===
13
14    /// Sets a gamepad button as pressed.
15    ///
16    /// # Panics
17    /// Panics if gamepad_id >= 4.
18    pub fn press_gamepad_button(&mut self, gamepad_id: usize, button: u32) {
19        self.ensure_gamepad_capacity(gamepad_id);
20
21        // Only buffer if this is a new press
22        let is_new = !self.gamepad_buttons_current[gamepad_id].contains(&button);
23        if is_new {
24            self.buffer_input(InputBinding::GamepadButton { gamepad_id, button });
25        }
26
27        self.gamepad_buttons_current[gamepad_id].insert(button);
28    }
29
30    /// Sets a gamepad button as released.
31    ///
32    /// # Panics
33    /// Panics if gamepad_id >= 4.
34    pub fn release_gamepad_button(&mut self, gamepad_id: usize, button: u32) {
35        self.ensure_gamepad_capacity(gamepad_id);
36        self.gamepad_buttons_current[gamepad_id].remove(&button);
37    }
38
39    /// Returns true if the gamepad button is currently pressed.
40    ///
41    /// Returns false if gamepad_id is invalid.
42    pub fn gamepad_button_pressed(&self, gamepad_id: usize, button: u32) -> bool {
43        self.gamepad_buttons_current
44            .get(gamepad_id)
45            .is_some_and(|buttons| buttons.contains(&button))
46    }
47
48    /// Returns true if the gamepad button was just pressed this frame.
49    pub fn gamepad_button_just_pressed(&self, gamepad_id: usize, button: u32) -> bool {
50        let current = self
51            .gamepad_buttons_current
52            .get(gamepad_id)
53            .is_some_and(|buttons| buttons.contains(&button));
54        let previous = self
55            .gamepad_buttons_previous
56            .get(gamepad_id)
57            .is_some_and(|buttons| buttons.contains(&button));
58        current && !previous
59    }
60
61    /// Returns true if the gamepad button was just released this frame.
62    pub fn gamepad_button_just_released(&self, gamepad_id: usize, button: u32) -> bool {
63        let current = self
64            .gamepad_buttons_current
65            .get(gamepad_id)
66            .is_some_and(|buttons| buttons.contains(&button));
67        let previous = self
68            .gamepad_buttons_previous
69            .get(gamepad_id)
70            .is_some_and(|buttons| buttons.contains(&button));
71        !current && previous
72    }
73
74    /// Ensures gamepad capacity for the given ID.
75    pub(super) fn ensure_gamepad_capacity(&mut self, gamepad_id: usize) {
76        while self.gamepad_buttons_current.len() <= gamepad_id {
77            self.gamepad_buttons_current
78                .push(std::collections::HashSet::new());
79        }
80        while self.gamepad_buttons_previous.len() <= gamepad_id {
81            self.gamepad_buttons_previous
82                .push(std::collections::HashSet::new());
83        }
84        while self.gamepads.len() <= gamepad_id {
85            self.gamepads.push(GamepadState::new());
86        }
87        while self.gamepads_previous.len() <= gamepad_id {
88            self.gamepads_previous.push(GamepadState::new());
89        }
90    }
91
92    // === Gamepad Analog Axes ===
93
94    /// Sets a gamepad analog axis value (-1.0 to 1.0).
95    ///
96    /// Values within the deadzone threshold are clamped to 0.0.
97    pub fn set_gamepad_axis(&mut self, gamepad_id: usize, axis: GamepadAxis, value: f32) {
98        self.ensure_gamepad_capacity(gamepad_id);
99
100        // Apply deadzone
101        let deadzone_value = if value.abs() < self.analog_deadzone {
102            0.0
103        } else {
104            value
105        };
106
107        self.gamepads[gamepad_id].axes.insert(axis, deadzone_value);
108    }
109
110    /// Returns the current value of a gamepad analog axis (-1.0 to 1.0).
111    ///
112    /// Returns 0.0 if the gamepad or axis doesn't exist.
113    pub fn gamepad_axis(&self, gamepad_id: usize, axis: GamepadAxis) -> f32 {
114        self.gamepads
115            .get(gamepad_id)
116            .and_then(|gamepad| gamepad.axes.get(&axis).copied())
117            .unwrap_or(0.0)
118    }
119
120    /// Returns the left stick as a Vec2 (x, y) where -1.0 is left/down and 1.0 is right/up.
121    ///
122    /// Returns Vec2::zero() if the gamepad doesn't exist.
123    pub fn gamepad_left_stick(&self, gamepad_id: usize) -> Vec2 {
124        Vec2::new(
125            self.gamepad_axis(gamepad_id, GamepadAxis::AxisLeftX),
126            self.gamepad_axis(gamepad_id, GamepadAxis::AxisLeftY),
127        )
128    }
129
130    /// Returns the right stick as a Vec2 (x, y) where -1.0 is left/down and 1.0 is right/up.
131    ///
132    /// Returns Vec2::zero() if the gamepad doesn't exist.
133    pub fn gamepad_right_stick(&self, gamepad_id: usize) -> Vec2 {
134        Vec2::new(
135            self.gamepad_axis(gamepad_id, GamepadAxis::AxisRightX),
136            self.gamepad_axis(gamepad_id, GamepadAxis::AxisRightY),
137        )
138    }
139
140    /// Returns the left trigger value (0.0 to 1.0).
141    ///
142    /// Returns 0.0 if the gamepad doesn't exist.
143    pub fn gamepad_left_trigger(&self, gamepad_id: usize) -> f32 {
144        // Left trigger is mapped to axis 2, normalized from -1.0..1.0 to 0.0..1.0
145        (self.gamepad_axis(gamepad_id, GamepadAxis::AxisLeftTrigger) + 1.0) * 0.5
146    }
147
148    /// Returns the right trigger value (0.0 to 1.0).
149    ///
150    /// Returns 0.0 if the gamepad doesn't exist.
151    pub fn gamepad_right_trigger(&self, gamepad_id: usize) -> f32 {
152        // Right trigger is mapped to axis 3, normalized from -1.0..1.0 to 0.0..1.0
153        (self.gamepad_axis(gamepad_id, GamepadAxis::AxisRightTrigger) + 1.0) * 0.5
154    }
155
156    // === Gamepad Connection ===
157
158    /// Sets the connection status of a gamepad.
159    pub fn set_gamepad_connected(&mut self, gamepad_id: usize, connected: bool) {
160        self.ensure_gamepad_capacity(gamepad_id);
161        self.gamepads[gamepad_id].connected = connected;
162    }
163
164    /// Returns true if the gamepad is currently connected.
165    pub fn is_gamepad_connected(&self, gamepad_id: usize) -> bool {
166        self.gamepads
167            .get(gamepad_id)
168            .map(|gamepad| gamepad.connected)
169            .unwrap_or(false)
170    }
171
172    /// Returns the number of connected gamepads.
173    pub fn connected_gamepad_count(&self) -> usize {
174        self.gamepads
175            .iter()
176            .filter(|gamepad| gamepad.connected)
177            .count()
178    }
179
180    /// Returns an iterator over all connected gamepad IDs.
181    pub fn connected_gamepads(&self) -> impl Iterator<Item = usize> + '_ {
182        self.gamepads
183            .iter()
184            .enumerate()
185            .filter_map(|(id, gamepad)| if gamepad.connected { Some(id) } else { None })
186    }
187
188    // === Gamepad Vibration ===
189
190    /// Sets the vibration intensity for a gamepad (0.0-1.0).
191    ///
192    /// Note: Actual vibration must be implemented by the platform layer.
193    /// This only tracks the requested vibration intensity.
194    pub fn set_gamepad_vibration(&mut self, gamepad_id: usize, intensity: f32) {
195        self.ensure_gamepad_capacity(gamepad_id);
196        self.gamepads[gamepad_id].vibration = intensity.clamp(0.0, 1.0);
197    }
198
199    /// Returns the current vibration intensity for a gamepad (0.0-1.0).
200    pub fn gamepad_vibration(&self, gamepad_id: usize) -> f32 {
201        self.gamepads
202            .get(gamepad_id)
203            .map(|gamepad| gamepad.vibration)
204            .unwrap_or(0.0)
205    }
206
207    /// Stops vibration for a gamepad (sets intensity to 0.0).
208    pub fn stop_gamepad_vibration(&mut self, gamepad_id: usize) {
209        self.set_gamepad_vibration(gamepad_id, 0.0);
210    }
211
212    /// Stops vibration for all gamepads.
213    pub fn stop_all_vibration(&mut self) {
214        for gamepad in &mut self.gamepads {
215            gamepad.vibration = 0.0;
216        }
217    }
218
219    // === Analog Deadzone ===
220
221    /// Returns the current analog deadzone threshold (0.0-1.0).
222    ///
223    /// Default is 0.1 (10%).
224    pub fn analog_deadzone(&self) -> f32 {
225        self.analog_deadzone
226    }
227
228    /// Sets the analog deadzone threshold (0.0-1.0).
229    ///
230    /// Analog axis values within this threshold are clamped to 0.0.
231    /// This prevents stick drift and accidental input.
232    pub fn set_analog_deadzone(&mut self, deadzone: f32) {
233        self.analog_deadzone = deadzone.clamp(0.0, 1.0);
234    }
235}