1use fixstr::FixStr;
6use limnus_app::prelude::{App, Plugin};
7use limnus_message::Messages;
8use limnus_message::prelude::Message;
9use limnus_resource::prelude::Resource;
10use std::collections::HashMap;
11use tracing::{debug, error, trace};
12
13#[repr(usize)]
14#[derive(Debug, Clone, Eq, PartialEq, Copy)]
15pub enum Button {
16 South,
18 East,
19 North,
20 West,
21
22 LeftTrigger,
24 LeftTrigger2,
25 RightTrigger,
26 RightTrigger2,
27
28 Select,
30 Start,
31 Mode, LeftThumb,
35 RightThumb,
36
37 DPadUp,
39 DPadDown,
40 DPadLeft,
41 DPadRight,
42}
43
44#[repr(usize)]
45#[derive(Debug, Clone, Eq, PartialEq, Copy)]
46pub enum Axis {
47 LeftStickX,
48 LeftStickY,
49 RightStickX,
50 RightStickY,
51}
52
53pub type GamePadId = usize;
54
55pub type AxisValueType = f32;
56pub type ButtonValueType = f32;
57
58#[derive(Debug, Message)]
60pub enum GamepadMessage {
61 Connected(GamePadId, String),
62 Disconnected(GamePadId),
63 Activated(GamePadId), ButtonChanged(GamePadId, Button, ButtonValueType),
65 AxisChanged(GamePadId, Axis, AxisValueType),
66}
67
68#[derive(Default, Debug, Clone)]
70pub struct Gamepad {
71 pub axis: [AxisValueType; 4],
72 pub buttons: [ButtonValueType; 17],
73 pub name: FixStr<64>,
74 pub id: GamePadId,
75 pub is_active: bool,
76}
77
78impl Gamepad {
79 #[must_use]
80 pub fn new(id: GamePadId, name: &str) -> Self {
81 let truncated_name: String = name.chars().take(32).collect();
82 Self {
83 axis: [0.0; 4],
84 buttons: [0.0; 17],
85 name: FixStr::new(&truncated_name).expect("gamepad name too long"), id,
87 is_active: false,
88 }
89 }
90
91 #[must_use]
93 pub fn is_pressed(&self, button: Button) -> bool {
94 self.buttons[button as usize] > 0.1
95 }
96
97 #[must_use]
98 pub const fn axis(&self, axis: Axis) -> AxisValueType {
99 self.axis[axis as usize]
100 }
101
102 #[must_use]
103 pub const fn button(&self, button: Button) -> ButtonValueType {
104 self.buttons[button as usize]
105 }
106}
107
108#[derive(Debug, Resource)]
109pub struct Gamepads {
110 gamepads: HashMap<GamePadId, Gamepad>,
111}
112
113impl Default for Gamepads {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl Gamepads {
120 #[must_use]
126 pub fn new() -> Self {
127 Self {
128 gamepads: HashMap::new(),
129 }
130 }
131 pub fn connected(&mut self, id: GamePadId, name: &str, queue: &mut Messages<GamepadMessage>) {
132 debug!(id=%id, name=name, "connected gamepad");
133 self.gamepads.insert(id, Gamepad::new(id, name));
134 queue.send(GamepadMessage::Connected(id, name.to_string()));
135 }
136
137 pub fn disconnected(&mut self, id: GamePadId, queue: &mut Messages<GamepadMessage>) {
138 if let Some(existing) = self.gamepads.remove(&id) {
139 debug!(id=%id, name=?existing.name, "disconnected gamepad");
140 queue.send(GamepadMessage::Disconnected(id));
141 } else {
142 error!(id=%id, "gamepad not found");
143 }
144 }
145
146 #[must_use]
147 pub fn gamepad(&self, id: GamePadId) -> Option<&Gamepad> {
148 self.gamepads.get(&id)
149 }
150
151 #[must_use]
153 pub fn axis(&self, id: GamePadId, axis: Axis) -> Option<AxisValueType> {
154 self.gamepad(id).map(|pad| pad.axis[axis as usize])
155 }
156
157 #[must_use]
159 pub fn button(&self, id: GamePadId, button: Button) -> Option<ButtonValueType> {
160 self.gamepad(id).map(|pad| pad.buttons[button as usize])
161 }
162
163 pub fn iter_active(&self) -> impl Iterator<Item = &Gamepad> {
164 self.gamepads.values().filter(|gamepad| gamepad.is_active)
165 }
166
167 pub fn iter(&self) -> impl Iterator<Item = &Gamepad> {
168 self.gamepads.values()
169 }
170
171 pub fn set_axis(
172 &mut self,
173 id: GamePadId,
174 axis: Axis,
175 value: AxisValueType,
176 queue: &mut Messages<GamepadMessage>,
177 ) -> Option<()> {
178 trace!(id=?id, axis=?axis, value=?value, "set axis");
179 let gamepad = self.gamepads.get_mut(&id)?;
180
181 queue.send(GamepadMessage::AxisChanged(id, axis, value));
182 gamepad.axis[axis as usize] = value;
183
184 Some(())
185 }
186
187 pub fn set_button(
188 &mut self,
189 id: GamePadId,
190 button: Button,
191 value: ButtonValueType,
192 queue: &mut Messages<GamepadMessage>,
193 ) -> Option<()> {
194 trace!(id=?id, button=?button, value=?value, "set button");
195
196 let gamepad = self.gamepads.get_mut(&id)?;
197
198 if !gamepad.is_active && value > 0.1 {
199 debug!(id=%id, button=?button, name=%gamepad.name, "gamepad activated");
200 queue.send(GamepadMessage::Activated(id));
201 gamepad.is_active = true;
202 }
203
204 queue.send(GamepadMessage::ButtonChanged(id, button, value));
205 gamepad.buttons[button as usize] = value;
206 Some(())
207 }
208
209 #[must_use]
211 pub fn name(&self, id: GamePadId) -> Option<&str> {
212 self.gamepad(id).map(|pad| pad.name.as_str())
213 }
214}
215
216pub struct GamepadResourcePlugin;
217
218impl Plugin for GamepadResourcePlugin {
219 fn build(&self, app: &mut App) {
220 app.insert_resource(Gamepads::new());
221 app.create_message_type::<GamepadMessage>();
222 }
223}