diff --git a/src/app.rs b/src/app.rs index 0196b15..3ef13fa 100644 --- a/src/app.rs +++ b/src/app.rs @@ -349,7 +349,10 @@ fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy, +} + pub struct GamepadMapping { buttons: HashMap, axes: HashMap, @@ -106,6 +114,7 @@ impl InputMapping { pub struct MappingProvider { device_mappings: Arc>>>>, sim_mappings: HashMap>>, + gamepad_info: Arc>>, } impl MappingProvider { @@ -136,6 +145,7 @@ impl MappingProvider { mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings))); Self { device_mappings: Arc::new(RwLock::new(HashMap::new())), + gamepad_info: Arc::new(RwLock::new(HashMap::new())), sim_mappings: mappings, } } @@ -144,24 +154,101 @@ impl MappingProvider { self.sim_mappings.get(&sim_id).unwrap() } - pub fn map_gamepad(&self, sim_id: SimId, gamepad: &Gamepad) { + pub fn handle_gamepad_connect(&self, gamepad: &Gamepad) { let device_id = DeviceId( gamepad.vendor_id().unwrap_or_default(), gamepad.product_id().unwrap_or_default(), ); let mut lock = self.device_mappings.write().unwrap(); - let gamepad_mapping = match lock.entry(device_id) { - Entry::Occupied(entry) => entry.get().clone(), + let mappings = match lock.entry(device_id) { Entry::Vacant(entry) => { let mappings = GamepadMapping::for_gamepad(gamepad); - entry.insert(Arc::new(RwLock::new(mappings))).clone() + entry.insert(Arc::new(RwLock::new(mappings))) } - }; + Entry::Occupied(entry) => entry.into_mut(), + } + .clone(); drop(lock); + let mut lock = self.gamepad_info.write().unwrap(); + let bound_to = SimId::values() + .into_iter() + .find(|sim_id| lock.values().all(|info| info.bound_to != Some(*sim_id))); + if let Entry::Vacant(entry) = lock.entry(gamepad.id()) { + let info = GamepadInfo { + id: *entry.key(), + name: gamepad.name().to_string(), + device_id, + bound_to, + }; + entry.insert(info); + } + drop(lock); + if let Some(sim_id) = bound_to { + self.for_sim(sim_id) + .write() + .unwrap() + .gamepads + .insert(gamepad.id(), mappings); + } + } + + pub fn handle_gamepad_disconnect(&self, gamepad_id: GamepadId) { + let mut lock = self.gamepad_info.write().unwrap(); + let Some(info) = lock.remove(&gamepad_id) else { + return; + }; + if let Some(sim_id) = info.bound_to { + self.for_sim(sim_id) + .write() + .unwrap() + .gamepads + .remove(&gamepad_id); + } + } + + pub fn assign_gamepad(&self, gamepad_id: GamepadId, sim_id: SimId) { + self.unassign_gamepad(gamepad_id); + let mut lock = self.gamepad_info.write().unwrap(); + let Some(info) = lock.get_mut(&gamepad_id) else { + return; + }; + info.bound_to = Some(sim_id); + let device_id = info.device_id; + drop(lock); + let Some(device_mappings) = self + .device_mappings + .write() + .unwrap() + .get(&device_id) + .cloned() + else { + return; + }; self.for_sim(sim_id) .write() .unwrap() .gamepads - .insert(gamepad.id(), gamepad_mapping); + .insert(gamepad_id, device_mappings); + } + + pub fn unassign_gamepad(&self, gamepad_id: GamepadId) { + let mut lock = self.gamepad_info.write().unwrap(); + let Some(info) = lock.get_mut(&gamepad_id) else { + return; + }; + if let Some(sim_id) = info.bound_to { + let mut sim_mapping = self.for_sim(sim_id).write().unwrap(); + sim_mapping.gamepads.remove(&gamepad_id); + } + info.bound_to = None; + } + + pub fn gamepad_info(&self) -> Vec { + self.gamepad_info + .read() + .unwrap() + .values() + .cloned() + .collect() } } diff --git a/src/window/input.rs b/src/window/input.rs index b2bc956..fe7ded5 100644 --- a/src/window/input.rs +++ b/src/window/input.rs @@ -42,7 +42,7 @@ impl InputWindow { } } - fn render_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { + fn show_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { let mappings = self.mappings.for_sim(sim_id); let binding_names = { let mapping = mappings.read().unwrap(); @@ -86,6 +86,37 @@ impl InputWindow { } }); } + + fn show_gamepads(&mut self, ui: &mut Ui) { + let mut gamepads = self.mappings.gamepad_info(); + gamepads.sort_by_key(|g| usize::from(g.id)); + + if gamepads.is_empty() { + ui.label("No gamepads connected."); + return; + } + + for (index, gamepad) in gamepads.into_iter().enumerate() { + ui.horizontal(|ui| { + ui.label(format!("Gamepad {index}: {}", gamepad.name)); + let mut bound_to = gamepad.bound_to; + let mut rebind = false; + rebind |= ui + .selectable_value(&mut bound_to, Some(SimId::Player1), "Player 1") + .changed(); + rebind |= ui + .selectable_value(&mut bound_to, Some(SimId::Player2), "Player 2") + .changed(); + rebind |= ui.selectable_value(&mut bound_to, None, "Nobody").changed(); + if rebind { + match bound_to { + Some(sim_id) => self.mappings.assign_gamepad(gamepad.id, sim_id), + None => self.mappings.unassign_gamepad(gamepad.id), + } + } + }); + } + } } impl AppWindow for InputWindow { @@ -104,12 +135,14 @@ impl AppWindow for InputWindow { ui.horizontal(|ui| { ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1"); ui.selectable_value(&mut self.active_tab, InputTab::Player2, "Player 2"); + ui.selectable_value(&mut self.active_tab, InputTab::Gamepads, "Gamepads"); }); }); CentralPanel::default().show(ctx, |ui| { match self.active_tab { - InputTab::Player1 => self.render_key_bindings(ui, SimId::Player1), - InputTab::Player2 => self.render_key_bindings(ui, SimId::Player2), + InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1), + InputTab::Player2 => self.show_key_bindings(ui, SimId::Player2), + InputTab::Gamepads => self.show_gamepads(ui), }; }); } @@ -130,4 +163,5 @@ impl AppWindow for InputWindow { enum InputTab { Player1, Player2, + Gamepads, }