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, Mappings}, }; use super::AppWindow; pub struct InputWindow { mappings: MappingProvider, now_binding: Option, active_tab: InputTab, } const KEY_NAMES: [(VBKey, &str); 14] = [ (VBKey::LU, "Up"), (VBKey::LD, "Down"), (VBKey::LL, "Left"), (VBKey::LR, "Right"), (VBKey::SEL, "Select"), (VBKey::STA, "Start"), (VBKey::B, "B"), (VBKey::A, "A"), (VBKey::LT, "L-Trigger"), (VBKey::RT, "R-Trigger"), (VBKey::RU, "R-Up"), (VBKey::RD, "R-Down"), (VBKey::RL, "R-Left"), (VBKey::RR, "R-Right"), ]; impl InputWindow { pub fn new(mappings: MappingProvider) -> Self { Self { mappings, now_binding: None, active_tab: InputTab::Player1, } } fn show_bindings( &mut self, ui: &mut Ui, mappings: &RwLock, bind_message: &str, ) { ui.horizontal(|ui| { if ui.button("Use defaults").clicked() { mappings.write().unwrap().use_default_mappings(); self.mappings.save(); self.now_binding = None; } if ui.button("Clear all").clicked() { mappings.write().unwrap().clear_all_mappings(); self.mappings.save(); self.now_binding = None; } }); ui.separator(); let mut names = { let mapping = mappings.read().unwrap(); mapping.mapping_names() }; TableBuilder::new(ui) .column(Column::remainder()) .column(Column::remainder()) .cell_layout(Layout::left_to_right(egui::Align::Center)) .body(|mut body| { for keys in KEY_NAMES.chunks_exact(2) { body.row(20.0, |mut row| { for (key, name) in keys { 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(*key) { bind_message } else { binding.as_deref().unwrap_or("") }; if ui .add_sized((width * 0.6, height), Button::new(label_text)) .clicked() { self.now_binding = Some(*key); } if ui .add_sized(ui.available_size(), Button::new("Clear")) .clicked() { let mut mapping = mappings.write().unwrap(); mapping.clear_mappings(*key); drop(mapping); self.mappings.save(); self.now_binding = None; } }); } }); } }); } 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)); 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), } } 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 { fn viewport_id(&self) -> ViewportId { ViewportId::from_hash_of("input") } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title("Bind Inputs") .with_inner_size((600.0, 400.0)) } 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| { match self.active_tab { 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), }; }); } fn handle_key_event(&mut self, event: &winit::event::KeyEvent) -> bool { if !event.state.is_pressed() { return false; } let sim_id = match self.active_tab { InputTab::Player1 => SimId::Player1, InputTab::Player2 => SimId::Player2, _ => { return false; } }; let Some(vb) = self.now_binding.take() else { return false; }; let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); mappings.add_keyboard_mapping(vb, event.physical_key); drop(mappings); self.mappings.save(); true } fn handle_gamepad_event(&mut self, event: &gilrs::Event) -> bool { let InputTab::RebindGamepad(gamepad_id) = self.active_tab else { return false; }; if gamepad_id != event.id { return false; } let Some(mappings) = self.mappings.for_gamepad(gamepad_id) else { return false; }; let Some(vb) = self.now_binding else { return false; }; match event.event { EventType::ButtonPressed(_, code) => { let mut mapping = mappings.write().unwrap(); mapping.add_button_mapping(vb, code); self.now_binding.take(); true } 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(); true } else if value > 0.75 { let mut mapping = mappings.write().unwrap(); mapping.add_axis_pos_mapping(vb, code); self.now_binding.take(); true } else { false } } _ => false, } } } #[derive(Clone, Copy, PartialEq, Eq)] enum InputTab { Player1, Player2, Gamepads, RebindGamepad(GamepadId), }