diff --git a/src/app.rs b/src/app.rs index 3ef13fa..b7c0fb0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,7 +8,7 @@ use egui::{ use gilrs::{EventType, Gilrs}; use winit::{ application::ApplicationHandler, - event::{KeyEvent, WindowEvent}, + event::WindowEvent, event_loop::{ActiveEventLoop, EventLoopProxy}, window::Window, }; @@ -83,7 +83,7 @@ impl ApplicationHandler for Application { match &event { WindowEvent::KeyboardInput { event, .. } => { self.controllers.handle_key_event(event); - viewport.handle_key_event(event); + viewport.app.handle_key_event(event); } WindowEvent::Focused(new_focused) => { self.focused = new_focused.then_some(viewport_id); @@ -143,7 +143,17 @@ impl ApplicationHandler for Application { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { - UserEvent::GamepadEvent(event) => self.controllers.handle_gamepad_event(&event), + UserEvent::GamepadEvent(event) => { + self.controllers.handle_gamepad_event(&event); + let Some(viewport) = self + .focused + .as_ref() + .and_then(|id| self.viewports.get_mut(id)) + else { + return; + }; + viewport.app.handle_gamepad_event(&event); + } UserEvent::OpenInput => { let input = InputWindow::new(self.mappings.clone()); self.open(event_loop, Box::new(input)); @@ -248,10 +258,6 @@ impl Viewport { } } - pub fn handle_key_event(&mut self, event: &KeyEvent) { - self.app.handle_key_event(event); - } - fn redraw(&mut self, event_loop: &ActiveEventLoop) -> Option { let mut input = self.state.take_egui_input(&self.window); input.viewports = std::iter::once((ViewportId::ROOT, self.info.clone())).collect(); diff --git a/src/input.rs b/src/input.rs index c604d4b..e818521 100644 --- a/src/input.rs +++ b/src/input.rs @@ -19,6 +19,11 @@ pub struct GamepadInfo { pub bound_to: Option, } +pub trait Mappings { + fn mapping_names(&self) -> HashMap>; + fn clear_mappings(&mut self, key: VBKey); +} + pub struct GamepadMapping { buttons: HashMap, axes: HashMap, @@ -54,6 +59,59 @@ impl GamepadMapping { Self { buttons, axes } } + + pub fn add_button_mapping(&mut self, key: VBKey, code: Code) { + let entry = self.buttons.entry(code).or_insert(VBKey::empty()); + *entry = entry.union(key); + } + + pub fn add_axis_neg_mapping(&mut self, key: VBKey, code: Code) { + let entry = self + .axes + .entry(code) + .or_insert((VBKey::empty(), VBKey::empty())); + entry.0 = entry.0.union(key); + } + + pub fn add_axis_pos_mapping(&mut self, key: VBKey, code: Code) { + let entry = self + .axes + .entry(code) + .or_insert((VBKey::empty(), VBKey::empty())); + entry.1 = entry.1.union(key); + } +} + +impl Mappings for GamepadMapping { + fn mapping_names(&self) -> HashMap> { + let mut results: HashMap> = HashMap::new(); + for (axis, (left_keys, right_keys)) in &self.axes { + for key in left_keys.iter() { + results.entry(key).or_default().push(format!("-{axis}")); + } + for key in right_keys.iter() { + results.entry(key).or_default().push(format!("+{axis}")); + } + } + for (button, keys) in &self.buttons { + for key in keys.iter() { + results.entry(key).or_default().push(format!("{button}")); + } + } + results + } + + fn clear_mappings(&mut self, key: VBKey) { + self.axes.retain(|_, (left, right)| { + *left = left.difference(key); + *right = right.difference(key); + !(left.is_empty() && right.is_empty()) + }); + self.buttons.retain(|_, keys| { + *keys = keys.difference(key); + !keys.is_empty() + }); + } } #[derive(Default)] @@ -81,15 +139,10 @@ impl InputMapping { let entry = self.keys.entry(keyboard_key).or_insert(VBKey::empty()); *entry = entry.union(key); } +} - pub fn clear_keyboard_mappings(&mut self, key: VBKey) { - self.keys.retain(|_, keys| { - *keys = keys.difference(key); - *keys != VBKey::empty() - }); - } - - pub fn keyboard_mapping_names(&self) -> HashMap { +impl Mappings for InputMapping { + fn mapping_names(&self) -> HashMap> { let mut results: HashMap> = HashMap::new(); for (keyboard_key, keys) in &self.keys { let name = match keyboard_key { @@ -101,12 +154,13 @@ impl InputMapping { } } results - .into_iter() - .map(|(k, mut v)| { - v.sort(); - (k, v.join(", ")) - }) - .collect() + } + + fn clear_mappings(&mut self, key: VBKey) { + self.keys.retain(|_, keys| { + *keys = keys.difference(key); + !keys.is_empty() + }); } } @@ -154,6 +208,14 @@ impl MappingProvider { self.sim_mappings.get(&sim_id).unwrap() } + pub fn for_gamepad(&self, gamepad_id: GamepadId) -> Option>> { + let lock = self.gamepad_info.read().unwrap(); + let device_id = lock.get(&gamepad_id)?.device_id; + drop(lock); + let lock = self.device_mappings.read().unwrap(); + lock.get(&device_id).cloned() + } + pub fn handle_gamepad_connect(&self, gamepad: &Gamepad) { let device_id = DeviceId( gamepad.vendor_id().unwrap_or_default(), diff --git a/src/window.rs b/src/window.rs index 8f0f179..fdc95d3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -18,4 +18,7 @@ pub trait AppWindow { fn handle_key_event(&mut self, event: &KeyEvent) { let _ = event; } + fn handle_gamepad_event(&mut self, event: &gilrs::Event) { + let _ = event; + } } diff --git a/src/window/input.rs b/src/window/input.rs index fe7ded5..2999e8b 100644 --- a/src/window/input.rs +++ b/src/window/input.rs @@ -2,17 +2,19 @@ use egui::{ Button, CentralPanel, Context, Label, Layout, TopBottomPanel, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, TableBuilder}; +use gilrs::{EventType, GamepadId}; +use std::sync::RwLock; use crate::{ emulator::{SimId, VBKey}, - input::MappingProvider, + input::{MappingProvider, Mappings}, }; use super::AppWindow; pub struct InputWindow { mappings: MappingProvider, - now_binding: Option<(SimId, VBKey)>, + now_binding: Option, active_tab: InputTab, } @@ -42,11 +44,15 @@ impl InputWindow { } } - fn show_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { - let mappings = self.mappings.for_sim(sim_id); - let binding_names = { + fn show_bindings( + &mut self, + ui: &mut Ui, + mappings: &RwLock, + bind_message: &str, + ) { + let mut names = { let mapping = mappings.read().unwrap(); - mapping.keyboard_mapping_names() + mapping.mapping_names() }; TableBuilder::new(ui) .column(Column::remainder()) @@ -56,29 +62,33 @@ impl InputWindow { for keys in KEY_NAMES.chunks_exact(2) { body.row(20.0, |mut row| { for (key, name) in keys { - let binding = binding_names.get(key).map(|s| s.as_str()); + let binding = names.remove(key).map(|mut s| { + s.sort(); + s.join(", ") + }); row.col(|ui| { let size = ui.available_size_before_wrap(); let width = size.x; let height = size.y; ui.add_sized((width * 0.2, height), Label::new(*name)); - let label_text = if self.now_binding == Some((sim_id, *key)) { - "Press any input" + let label_text = if self.now_binding == Some(*key) { + bind_message } else { - binding.unwrap_or("") + binding.as_deref().unwrap_or("") }; if ui .add_sized((width * 0.6, height), Button::new(label_text)) .clicked() { - self.now_binding = Some((sim_id, *key)) + self.now_binding = Some(*key); } if ui .add_sized(ui.available_size(), Button::new("Clear")) .clicked() { let mut mapping = mappings.write().unwrap(); - mapping.clear_keyboard_mappings(*key); + mapping.clear_mappings(*key); + self.now_binding = None; } }); } @@ -87,6 +97,11 @@ impl InputWindow { }); } + fn show_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { + let mappings = self.mappings.for_sim(sim_id).clone(); + self.show_bindings(ui, &mappings, "Press any key"); + } + fn show_gamepads(&mut self, ui: &mut Ui) { let mut gamepads = self.mappings.gamepad_info(); gamepads.sort_by_key(|g| usize::from(g.id)); @@ -114,9 +129,21 @@ impl InputWindow { None => self.mappings.unassign_gamepad(gamepad.id), } } + ui.separator(); + if ui.button("Rebind").clicked() { + self.active_tab = InputTab::RebindGamepad(gamepad.id); + } }); } } + + fn show_gamepad_bindings(&mut self, ui: &mut Ui, gamepad_id: GamepadId) { + let Some(mappings) = self.mappings.for_gamepad(gamepad_id) else { + self.active_tab = InputTab::Gamepads; + return; + }; + self.show_bindings(ui, &mappings, "Press any input"); + } } impl AppWindow for InputWindow { @@ -133,9 +160,17 @@ impl AppWindow for InputWindow { fn show(&mut self, ctx: &Context) { TopBottomPanel::top("options").show(ctx, |ui| { ui.horizontal(|ui| { + let old_active_tab = self.active_tab; 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"); + if matches!(self.active_tab, InputTab::RebindGamepad(_)) { + let tab = self.active_tab; + ui.selectable_value(&mut self.active_tab, tab, "Rebind Gamepad"); + } + if old_active_tab != self.active_tab { + self.now_binding = None; + } }); }); CentralPanel::default().show(ctx, |ui| { @@ -143,6 +178,7 @@ impl AppWindow for InputWindow { InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1), InputTab::Player2 => self.show_key_bindings(ui, SimId::Player2), InputTab::Gamepads => self.show_gamepads(ui), + InputTab::RebindGamepad(id) => self.show_gamepad_bindings(ui, id), }; }); } @@ -151,12 +187,54 @@ impl AppWindow for InputWindow { if !event.state.is_pressed() { return; } - let Some((sim_id, vb)) = self.now_binding.take() else { + let sim_id = match self.active_tab { + InputTab::Player1 => SimId::Player1, + InputTab::Player2 => SimId::Player2, + _ => { + return; + } + }; + let Some(vb) = self.now_binding.take() else { return; }; let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); mappings.add_keyboard_mapping(vb, event.physical_key); } + + fn handle_gamepad_event(&mut self, event: &gilrs::Event) { + let InputTab::RebindGamepad(gamepad_id) = self.active_tab else { + return; + }; + if gamepad_id != event.id { + return; + } + let Some(mappings) = self.mappings.for_gamepad(gamepad_id) else { + return; + }; + let Some(vb) = self.now_binding else { + return; + }; + match event.event { + EventType::ButtonPressed(_, code) => { + let mut mapping = mappings.write().unwrap(); + mapping.add_button_mapping(vb, code); + self.now_binding.take(); + } + EventType::AxisChanged(_, value, code) => { + if value < -0.75 { + let mut mapping = mappings.write().unwrap(); + mapping.add_axis_neg_mapping(vb, code); + self.now_binding.take(); + } + if value > 0.75 { + let mut mapping = mappings.write().unwrap(); + mapping.add_axis_pos_mapping(vb, code); + self.now_binding.take(); + } + } + _ => {} + } + } } #[derive(Clone, Copy, PartialEq, Eq)] @@ -164,4 +242,5 @@ enum InputTab { Player1, Player2, Gamepads, + RebindGamepad(GamepadId), }