From 5cb36d0bcc00229031c2b887e980ad0de67982d8 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sun, 10 Nov 2024 14:05:10 -0500 Subject: [PATCH] Functional input binding --- src/app.rs | 62 ++++++++++++++---------- src/app/common.rs | 11 ++++- src/app/game.rs | 46 +++++++++++------- src/app/input.rs | 105 ++++++++++++++++++++++++++++++++++++----- src/controller.rs | 36 +++++--------- src/input.rs | 71 ++++++++++++++++++++++++++++ src/main.rs | 1 + src/shrooms_vb_core.rs | 3 +- 8 files changed, 254 insertions(+), 81 deletions(-) create mode 100644 src/input.rs diff --git a/src/app.rs b/src/app.rs index 421fbd8..a5d62ac 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,8 @@ -use std::{collections::HashMap, fmt::Debug}; +use std::{ + collections::HashMap, + fmt::Debug, + sync::{Arc, RwLock}, +}; use game::GameWindow; use winit::{ @@ -8,7 +12,11 @@ use winit::{ window::WindowId, }; -use crate::emulator::EmulatorClient; +use crate::{ + controller::ControllerState, + emulator::{EmulatorClient, EmulatorCommand}, + input::InputMapper, +}; mod common; mod game; @@ -16,32 +24,35 @@ mod input; pub struct App { windows: HashMap>, - focused_window: Option, client: EmulatorClient, + input_mapper: Arc>, + controller: ControllerState, proxy: EventLoopProxy, } impl App { pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { + let input_mapper = Arc::new(RwLock::new(InputMapper::new())); + let controller = ControllerState::new(input_mapper.clone()); Self { windows: HashMap::new(), - focused_window: None, client, + input_mapper, + controller, proxy, } } - - fn active_window(&mut self) -> Option<&mut Box> { - let active_window = self.focused_window?; - self.windows.get_mut(&active_window) - } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let mut window = GameWindow::new(event_loop, self.client.clone(), self.proxy.clone()); + let mut window = GameWindow::new( + event_loop, + self.client.clone(), + self.input_mapper.clone(), + self.proxy.clone(), + ); window.init(); - self.focused_window = Some(window.id()); self.windows.insert(window.id(), Box::new(window)); } @@ -51,11 +62,10 @@ impl ApplicationHandler for App { window_id: WindowId, event: WindowEvent, ) { - if let WindowEvent::Focused(focused) = event { - if focused { - self.focused_window = Some(window_id); - } else { - self.focused_window = None; + if let WindowEvent::KeyboardInput { event, .. } = &event { + if self.controller.key_event(event) { + self.client + .send_command(EmulatorCommand::SetKeys(self.controller.pressed())); } } let Some(window) = self.windows.get_mut(&window_id) else { @@ -82,17 +92,21 @@ impl ApplicationHandler for App { device_id: winit::event::DeviceId, event: winit::event::DeviceEvent, ) { - let Some(window) = self.active_window() else { - return; - }; - window.handle_event(event_loop, &Event::DeviceEvent { device_id, event }); + for window in self.windows.values_mut() { + window.handle_event( + event_loop, + &Event::DeviceEvent { + device_id, + event: event.clone(), + }, + ); + } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - let Some(window) = self.active_window() else { - return; - }; - window.handle_event(event_loop, &Event::AboutToWait); + for window in self.windows.values_mut() { + window.handle_event(event_loop, &Event::AboutToWait); + } } } diff --git a/src/app/common.rs b/src/app/common.rs index 9aaf8b0..a5ec14f 100644 --- a/src/app/common.rs +++ b/src/app/common.rs @@ -235,14 +235,23 @@ impl<'a> Drop for ContextLock<'a> { pub trait UiExt { fn fullscreen_window(&self) -> Option>; + fn right_align_text>(&self, text: T, space: f32); } impl UiExt for imgui::Ui { fn fullscreen_window(&self) -> Option> { self.window("fullscreen") .position([0.0, 0.0], imgui::Condition::Always) - .size(self.window_size(), imgui::Condition::Always) + .size(self.io().display_size, imgui::Condition::Always) .flags(imgui::WindowFlags::NO_DECORATION) .begin() } + + fn right_align_text>(&self, text: T, space: f32) { + let width = self.calc_text_size(text.as_ref())[0]; + let [left, y] = self.cursor_pos(); + let right = left + space; + self.set_cursor_pos([right - width, y]); + self.text(text); + } } diff --git a/src/app/game.rs b/src/app/game.rs index 41c50cc..5b00c51 100644 --- a/src/app/game.rs +++ b/src/app/game.rs @@ -1,15 +1,18 @@ -use std::{sync::Arc, time::Instant}; +use std::{ + sync::{Arc, RwLock}, + time::Instant, +}; use wgpu::util::DeviceExt as _; use winit::{ dpi::LogicalSize, - event::{Event, KeyEvent, WindowEvent}, + event::{Event, WindowEvent}, event_loop::{ActiveEventLoop, EventLoopProxy}, window::WindowId, }; use crate::{ - controller::ControllerState, emulator::{EmulatorClient, EmulatorCommand}, + input::InputMapper, renderer::GameRenderer, }; @@ -25,7 +28,7 @@ pub struct GameWindow { pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, client: EmulatorClient, - controller: ControllerState, + input_mapper: Arc>, proxy: EventLoopProxy, } @@ -33,6 +36,7 @@ impl GameWindow { pub fn new( event_loop: &ActiveEventLoop, client: EmulatorClient, + input_mapper: Arc>, proxy: EventLoopProxy, ) -> Self { let window = WindowStateBuilder::new(event_loop) @@ -153,15 +157,13 @@ impl GameWindow { cache: None, }); - let controller = ControllerState::new(); - client.send_command(EmulatorCommand::SetKeys(controller.pressed())); Self { window, imgui: None, pipeline: render_pipeline, bind_group, client, - controller, + input_mapper, proxy, } } @@ -170,6 +172,7 @@ impl GameWindow { let window = &mut self.window; let imgui = self.imgui.as_mut().unwrap(); let mut context = imgui.context.lock().unwrap(); + let mut new_size = None; let now = Instant::now(); context.io_mut().update_delta_time(now - imgui.last_frame); @@ -226,13 +229,18 @@ impl GameWindow { if ui.menu_item_config(label).selected(selected).build() { if let Some(size) = window.window.request_inner_size(dims) { window.handle_resize(&size); + new_size = Some(size); } } } }); ui.menu("Input", || { - if ui.menu_item("Map Input") { - let input_window = Box::new(InputWindow::new(event_loop, self.proxy.clone())); + if ui.menu_item("Bind Inputs") { + let input_window = Box::new(InputWindow::new( + event_loop, + self.input_mapper.clone(), + self.proxy.clone(), + )); self.proxy .send_event(UserEvent::OpenWindow(input_window)) .unwrap(); @@ -287,17 +295,21 @@ impl GameWindow { drop(rpass); + if let Some(size) = new_size { + imgui.platform.handle_event::( + context.io_mut(), + &window.window, + &Event::WindowEvent { + window_id: window.window.id(), + event: WindowEvent::Resized(size), + }, + ); + } + window.queue.submit(Some(encoder.finish())); frame.present(); } - - fn handle_key_event(&mut self, event: &KeyEvent) { - if self.controller.key_event(event) { - self.client - .send_command(EmulatorCommand::SetKeys(self.controller.pressed())); - } - } } impl AppWindow for GameWindow { @@ -307,6 +319,7 @@ impl AppWindow for GameWindow { fn init(&mut self) { self.imgui = Some(ImguiState::new(&self.window)); + self.window.window.request_redraw(); } fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event) { @@ -314,7 +327,6 @@ impl AppWindow for GameWindow { Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(size) => self.window.handle_resize(size), WindowEvent::CloseRequested => event_loop.exit(), - WindowEvent::KeyboardInput { event, .. } => self.handle_key_event(event), WindowEvent::RedrawRequested => self.draw(event_loop), _ => (), }, diff --git a/src/app/input.rs b/src/app/input.rs index 276f297..bf56196 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -1,29 +1,62 @@ -use std::time::Instant; - -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ActiveEventLoop, EventLoopProxy}, +use std::{ + sync::{Arc, RwLock}, + time::Instant, }; +use winit::{ + dpi::LogicalSize, + event::{Event, KeyEvent, WindowEvent}, + event_loop::{ActiveEventLoop, EventLoopProxy}, + platform::modifier_supplement::KeyEventExtModifierSupplement, +}; + +use crate::{input::InputMapper, shrooms_vb_core::VBKey}; + use super::{ - common::{ImguiState, UiExt as _, WindowState, WindowStateBuilder}, + common::{ImguiState, UiExt, WindowState, WindowStateBuilder}, AppWindow, UserEvent, }; pub struct InputWindow { window: WindowState, imgui: Option, + input_mapper: Arc>, proxy: EventLoopProxy, + now_binding: Option, } +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(event_loop: &ActiveEventLoop, proxy: EventLoopProxy) -> Self { + pub fn new( + event_loop: &ActiveEventLoop, + input_mapper: Arc>, + proxy: EventLoopProxy, + ) -> Self { let window = WindowStateBuilder::new(event_loop) - .with_title("Map Inputs") + .with_title("Bind Inputs") + .with_inner_size(LogicalSize::new(600, 400)) .build(); Self { window, imgui: None, + input_mapper, + now_binding: None, proxy, } } @@ -50,17 +83,50 @@ impl InputWindow { .expect("Failed to prepare frame"); let ui = context.new_frame(); - if let Some(window) = ui.fullscreen_window() { + let mut render_key_bindings = || { if let Some(table) = ui.begin_table("controls", 2) { + let binding_names = { + let mapper = self.input_mapper.read().unwrap(); + mapper.binding_names() + }; ui.table_next_row(); - ui.table_next_column(); - ui.text("Key"); + for (key, name) in KEY_NAMES { + let binding = binding_names.get(&key).map(|s| s.as_str()); + ui.table_next_column(); + let [space, _] = ui.content_region_avail(); + ui.group(|| { + ui.right_align_text(name, space * 0.20); + ui.same_line(); + let label_text = if self.now_binding == Some(key) { + "Press any input" + } else { + binding.unwrap_or("") + }; + let label = format!("{}##{}", label_text, name); + if ui.button_with_size(label, [space * 0.60, 0.0]) { + self.now_binding = Some(key); + } + }); + ui.same_line(); + if ui.button(format!("Clear##{name}")) { + let mut mapper = self.input_mapper.write().unwrap(); + mapper.clear_binding(key); + } + } - ui.table_next_column(); - ui.text("Value"); table.end(); } + }; + + if let Some(window) = ui.fullscreen_window() { + if let Some(tabs) = ui.tab_bar("tabs") { + if let Some(tab) = ui.tab_item("Player 1") { + render_key_bindings(); + tab.end(); + } + tabs.end(); + } window.end(); } let mut encoder: wgpu::CommandEncoder = window @@ -102,6 +168,17 @@ impl InputWindow { frame.present(); } + + fn try_bind_key(&mut self, event: &KeyEvent) { + if !event.state.is_pressed() { + return; + } + let Some(vb) = self.now_binding.take() else { + return; + }; + let mut mapper = self.input_mapper.write().unwrap(); + mapper.bind_key(vb, event.key_without_modifiers()); + } } impl AppWindow for InputWindow { @@ -111,6 +188,7 @@ impl AppWindow for InputWindow { fn init(&mut self) { self.imgui = Some(ImguiState::new(&self.window)); + self.window.window.request_redraw(); } fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event) { @@ -121,6 +199,7 @@ impl AppWindow for InputWindow { .proxy .send_event(UserEvent::CloseWindow(self.id())) .unwrap(), + WindowEvent::KeyboardInput { event, .. } => self.try_bind_key(event), WindowEvent::RedrawRequested => self.draw(), _ => (), }, diff --git a/src/controller.rs b/src/controller.rs index 1e8995e..70861ab 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,17 +1,18 @@ -use winit::{ - event::{ElementState, KeyEvent}, - keyboard::{Key, NamedKey}, -}; +use std::sync::{Arc, RwLock}; -use crate::shrooms_vb_core::VBKey; +use winit::event::{ElementState, KeyEvent}; + +use crate::{input::InputMapper, shrooms_vb_core::VBKey}; pub struct ControllerState { + input_mapper: Arc>, pressed: VBKey, } impl ControllerState { - pub fn new() -> Self { + pub fn new(input_mapper: Arc>) -> Self { Self { + input_mapper, pressed: VBKey::SGN, } } @@ -21,7 +22,7 @@ impl ControllerState { } pub fn key_event(&mut self, event: &KeyEvent) -> bool { - let Some(input) = self.key_to_input(&event.logical_key) else { + let Some(input) = self.key_event_to_input(event) else { return false; }; match event.state { @@ -42,23 +43,8 @@ impl ControllerState { } } - fn key_to_input(&self, key: &Key) -> Option { - match key.as_ref() { - Key::Character("a") => Some(VBKey::SEL), - Key::Character("s") => Some(VBKey::STA), - Key::Character("d") => Some(VBKey::B), - Key::Character("f") => Some(VBKey::A), - Key::Character("e") => Some(VBKey::LT), - Key::Character("r") => Some(VBKey::RT), - Key::Character("i") => Some(VBKey::RU), - Key::Character("j") => Some(VBKey::RL), - Key::Character("k") => Some(VBKey::RD), - Key::Character("l") => Some(VBKey::RR), - Key::Named(NamedKey::ArrowUp) => Some(VBKey::LU), - Key::Named(NamedKey::ArrowLeft) => Some(VBKey::LL), - Key::Named(NamedKey::ArrowDown) => Some(VBKey::LD), - Key::Named(NamedKey::ArrowRight) => Some(VBKey::LR), - _ => None, - } + fn key_event_to_input(&self, event: &KeyEvent) -> Option { + let mapper = self.input_mapper.read().unwrap(); + mapper.key_event(event) } } diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..5367d60 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; + +use winit::{ + event::KeyEvent, + keyboard::{Key, NamedKey}, + platform::modifier_supplement::KeyEventExtModifierSupplement, +}; + +use crate::shrooms_vb_core::VBKey; + +pub struct InputMapper { + vb_bindings: HashMap, + key_bindings: HashMap, +} + +impl InputMapper { + pub fn new() -> Self { + let mut mapper = Self { + vb_bindings: HashMap::new(), + key_bindings: HashMap::new(), + }; + mapper.bind_key(VBKey::SEL, Key::Character("a".into())); + mapper.bind_key(VBKey::STA, Key::Character("s".into())); + mapper.bind_key(VBKey::B, Key::Character("d".into())); + mapper.bind_key(VBKey::A, Key::Character("f".into())); + mapper.bind_key(VBKey::LT, Key::Character("e".into())); + mapper.bind_key(VBKey::RT, Key::Character("r".into())); + mapper.bind_key(VBKey::RU, Key::Character("i".into())); + mapper.bind_key(VBKey::RL, Key::Character("j".into())); + mapper.bind_key(VBKey::RD, Key::Character("k".into())); + mapper.bind_key(VBKey::RR, Key::Character("l".into())); + mapper.bind_key(VBKey::LU, Key::Named(NamedKey::ArrowUp)); + mapper.bind_key(VBKey::LL, Key::Named(NamedKey::ArrowLeft)); + mapper.bind_key(VBKey::LD, Key::Named(NamedKey::ArrowDown)); + mapper.bind_key(VBKey::LR, Key::Named(NamedKey::ArrowRight)); + mapper + } + + pub fn binding_names(&self) -> HashMap { + self.vb_bindings + .iter() + .map(|(k, v)| { + let name = match v { + Key::Character(char) => char.to_string(), + Key::Named(key) => format!("{:?}", key), + k => format!("{:?}", k), + }; + (*k, name) + }) + .collect() + } + + pub fn bind_key(&mut self, vb: VBKey, key: Key) { + if let Some(old) = self.vb_bindings.insert(vb, key.clone()) { + self.key_bindings.remove(&old); + } + self.key_bindings.insert(key, vb); + } + + pub fn clear_binding(&mut self, vb: VBKey) { + if let Some(old) = self.vb_bindings.remove(&vb) { + self.key_bindings.remove(&old); + } + } + + pub fn key_event(&self, event: &KeyEvent) -> Option { + self.key_bindings + .get(&event.key_without_modifiers()) + .cloned() + } +} diff --git a/src/main.rs b/src/main.rs index bca1441..1b79b00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod app; mod audio; mod controller; mod emulator; +mod input; mod renderer; mod shrooms_vb_core; diff --git a/src/shrooms_vb_core.rs b/src/shrooms_vb_core.rs index 8fb1789..324689d 100644 --- a/src/shrooms_vb_core.rs +++ b/src/shrooms_vb_core.rs @@ -26,7 +26,8 @@ enum VBDataType { } bitflags! { - #[derive(Clone, Copy, Debug)] + #[repr(transparent)] + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct VBKey: u16 { const PWR = 0x0001; const SGN = 0x0002;