Support rebinding gamepad inputs
This commit is contained in:
parent
24474fabd0
commit
ce7ba71ea0
20
src/app.rs
20
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<UserEvent> 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<UserEvent> 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<Action> {
|
||||
let mut input = self.state.take_egui_input(&self.window);
|
||||
input.viewports = std::iter::once((ViewportId::ROOT, self.info.clone())).collect();
|
||||
|
|
88
src/input.rs
88
src/input.rs
|
@ -19,6 +19,11 @@ pub struct GamepadInfo {
|
|||
pub bound_to: Option<SimId>,
|
||||
}
|
||||
|
||||
pub trait Mappings {
|
||||
fn mapping_names(&self) -> HashMap<VBKey, Vec<String>>;
|
||||
fn clear_mappings(&mut self, key: VBKey);
|
||||
}
|
||||
|
||||
pub struct GamepadMapping {
|
||||
buttons: HashMap<Code, VBKey>,
|
||||
axes: HashMap<Code, (VBKey, VBKey)>,
|
||||
|
@ -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<VBKey, Vec<String>> {
|
||||
let mut results: HashMap<VBKey, Vec<String>> = 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<VBKey, String> {
|
||||
impl Mappings for InputMapping {
|
||||
fn mapping_names(&self) -> HashMap<VBKey, Vec<String>> {
|
||||
let mut results: HashMap<VBKey, Vec<String>> = 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<Arc<RwLock<GamepadMapping>>> {
|
||||
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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<VBKey>,
|
||||
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<T: Mappings>(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
mappings: &RwLock<T>,
|
||||
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),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue