Functional input binding
This commit is contained in:
parent
a69247dd33
commit
5cb36d0bcc
62
src/app.rs
62
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 game::GameWindow;
|
||||||
use winit::{
|
use winit::{
|
||||||
|
@ -8,7 +12,11 @@ use winit::{
|
||||||
window::WindowId,
|
window::WindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::emulator::EmulatorClient;
|
use crate::{
|
||||||
|
controller::ControllerState,
|
||||||
|
emulator::{EmulatorClient, EmulatorCommand},
|
||||||
|
input::InputMapper,
|
||||||
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
mod game;
|
mod game;
|
||||||
|
@ -16,32 +24,35 @@ mod input;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
windows: HashMap<WindowId, Box<dyn AppWindow>>,
|
windows: HashMap<WindowId, Box<dyn AppWindow>>,
|
||||||
focused_window: Option<WindowId>,
|
|
||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
|
input_mapper: Arc<RwLock<InputMapper>>,
|
||||||
|
controller: ControllerState,
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
|
pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
|
||||||
|
let input_mapper = Arc::new(RwLock::new(InputMapper::new()));
|
||||||
|
let controller = ControllerState::new(input_mapper.clone());
|
||||||
Self {
|
Self {
|
||||||
windows: HashMap::new(),
|
windows: HashMap::new(),
|
||||||
focused_window: None,
|
|
||||||
client,
|
client,
|
||||||
|
input_mapper,
|
||||||
|
controller,
|
||||||
proxy,
|
proxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_window(&mut self) -> Option<&mut Box<dyn AppWindow>> {
|
|
||||||
let active_window = self.focused_window?;
|
|
||||||
self.windows.get_mut(&active_window)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationHandler<UserEvent> for App {
|
impl ApplicationHandler<UserEvent> for App {
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
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();
|
window.init();
|
||||||
self.focused_window = Some(window.id());
|
|
||||||
self.windows.insert(window.id(), Box::new(window));
|
self.windows.insert(window.id(), Box::new(window));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +62,10 @@ impl ApplicationHandler<UserEvent> for App {
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
event: WindowEvent,
|
event: WindowEvent,
|
||||||
) {
|
) {
|
||||||
if let WindowEvent::Focused(focused) = event {
|
if let WindowEvent::KeyboardInput { event, .. } = &event {
|
||||||
if focused {
|
if self.controller.key_event(event) {
|
||||||
self.focused_window = Some(window_id);
|
self.client
|
||||||
} else {
|
.send_command(EmulatorCommand::SetKeys(self.controller.pressed()));
|
||||||
self.focused_window = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let Some(window) = self.windows.get_mut(&window_id) else {
|
let Some(window) = self.windows.get_mut(&window_id) else {
|
||||||
|
@ -82,17 +92,21 @@ impl ApplicationHandler<UserEvent> for App {
|
||||||
device_id: winit::event::DeviceId,
|
device_id: winit::event::DeviceId,
|
||||||
event: winit::event::DeviceEvent,
|
event: winit::event::DeviceEvent,
|
||||||
) {
|
) {
|
||||||
let Some(window) = self.active_window() else {
|
for window in self.windows.values_mut() {
|
||||||
return;
|
window.handle_event(
|
||||||
};
|
event_loop,
|
||||||
window.handle_event(event_loop, &Event::DeviceEvent { device_id, event });
|
&Event::DeviceEvent {
|
||||||
|
device_id,
|
||||||
|
event: event.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
let Some(window) = self.active_window() else {
|
for window in self.windows.values_mut() {
|
||||||
return;
|
window.handle_event(event_loop, &Event::AboutToWait);
|
||||||
};
|
}
|
||||||
window.handle_event(event_loop, &Event::AboutToWait);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -235,14 +235,23 @@ impl<'a> Drop for ContextLock<'a> {
|
||||||
|
|
||||||
pub trait UiExt {
|
pub trait UiExt {
|
||||||
fn fullscreen_window(&self) -> Option<WindowToken<'_>>;
|
fn fullscreen_window(&self) -> Option<WindowToken<'_>>;
|
||||||
|
fn right_align_text<T: AsRef<str>>(&self, text: T, space: f32);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiExt for imgui::Ui {
|
impl UiExt for imgui::Ui {
|
||||||
fn fullscreen_window(&self) -> Option<WindowToken<'_>> {
|
fn fullscreen_window(&self) -> Option<WindowToken<'_>> {
|
||||||
self.window("fullscreen")
|
self.window("fullscreen")
|
||||||
.position([0.0, 0.0], imgui::Condition::Always)
|
.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)
|
.flags(imgui::WindowFlags::NO_DECORATION)
|
||||||
.begin()
|
.begin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn right_align_text<T: AsRef<str>>(&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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
use wgpu::util::DeviceExt as _;
|
use wgpu::util::DeviceExt as _;
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
event::{Event, KeyEvent, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||||
window::WindowId,
|
window::WindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
controller::ControllerState,
|
|
||||||
emulator::{EmulatorClient, EmulatorCommand},
|
emulator::{EmulatorClient, EmulatorCommand},
|
||||||
|
input::InputMapper,
|
||||||
renderer::GameRenderer,
|
renderer::GameRenderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,7 +28,7 @@ pub struct GameWindow {
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
bind_group: wgpu::BindGroup,
|
bind_group: wgpu::BindGroup,
|
||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
controller: ControllerState,
|
input_mapper: Arc<RwLock<InputMapper>>,
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +36,7 @@ impl GameWindow {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
|
input_mapper: Arc<RwLock<InputMapper>>,
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let window = WindowStateBuilder::new(event_loop)
|
let window = WindowStateBuilder::new(event_loop)
|
||||||
|
@ -153,15 +157,13 @@ impl GameWindow {
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let controller = ControllerState::new();
|
|
||||||
client.send_command(EmulatorCommand::SetKeys(controller.pressed()));
|
|
||||||
Self {
|
Self {
|
||||||
window,
|
window,
|
||||||
imgui: None,
|
imgui: None,
|
||||||
pipeline: render_pipeline,
|
pipeline: render_pipeline,
|
||||||
bind_group,
|
bind_group,
|
||||||
client,
|
client,
|
||||||
controller,
|
input_mapper,
|
||||||
proxy,
|
proxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,6 +172,7 @@ impl GameWindow {
|
||||||
let window = &mut self.window;
|
let window = &mut self.window;
|
||||||
let imgui = self.imgui.as_mut().unwrap();
|
let imgui = self.imgui.as_mut().unwrap();
|
||||||
let mut context = imgui.context.lock().unwrap();
|
let mut context = imgui.context.lock().unwrap();
|
||||||
|
let mut new_size = None;
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
context.io_mut().update_delta_time(now - imgui.last_frame);
|
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 ui.menu_item_config(label).selected(selected).build() {
|
||||||
if let Some(size) = window.window.request_inner_size(dims) {
|
if let Some(size) = window.window.request_inner_size(dims) {
|
||||||
window.handle_resize(&size);
|
window.handle_resize(&size);
|
||||||
|
new_size = Some(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.menu("Input", || {
|
ui.menu("Input", || {
|
||||||
if ui.menu_item("Map Input") {
|
if ui.menu_item("Bind Inputs") {
|
||||||
let input_window = Box::new(InputWindow::new(event_loop, self.proxy.clone()));
|
let input_window = Box::new(InputWindow::new(
|
||||||
|
event_loop,
|
||||||
|
self.input_mapper.clone(),
|
||||||
|
self.proxy.clone(),
|
||||||
|
));
|
||||||
self.proxy
|
self.proxy
|
||||||
.send_event(UserEvent::OpenWindow(input_window))
|
.send_event(UserEvent::OpenWindow(input_window))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -287,17 +295,21 @@ impl GameWindow {
|
||||||
|
|
||||||
drop(rpass);
|
drop(rpass);
|
||||||
|
|
||||||
|
if let Some(size) = new_size {
|
||||||
|
imgui.platform.handle_event::<UserEvent>(
|
||||||
|
context.io_mut(),
|
||||||
|
&window.window,
|
||||||
|
&Event::WindowEvent {
|
||||||
|
window_id: window.window.id(),
|
||||||
|
event: WindowEvent::Resized(size),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
window.queue.submit(Some(encoder.finish()));
|
window.queue.submit(Some(encoder.finish()));
|
||||||
|
|
||||||
frame.present();
|
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 {
|
impl AppWindow for GameWindow {
|
||||||
|
@ -307,6 +319,7 @@ impl AppWindow for GameWindow {
|
||||||
|
|
||||||
fn init(&mut self) {
|
fn init(&mut self) {
|
||||||
self.imgui = Some(ImguiState::new(&self.window));
|
self.imgui = Some(ImguiState::new(&self.window));
|
||||||
|
self.window.window.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event<UserEvent>) {
|
fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event<UserEvent>) {
|
||||||
|
@ -314,7 +327,6 @@ impl AppWindow for GameWindow {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
WindowEvent::Resized(size) => self.window.handle_resize(size),
|
WindowEvent::Resized(size) => self.window.handle_resize(size),
|
||||||
WindowEvent::CloseRequested => event_loop.exit(),
|
WindowEvent::CloseRequested => event_loop.exit(),
|
||||||
WindowEvent::KeyboardInput { event, .. } => self.handle_key_event(event),
|
|
||||||
WindowEvent::RedrawRequested => self.draw(event_loop),
|
WindowEvent::RedrawRequested => self.draw(event_loop),
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
|
105
src/app/input.rs
105
src/app/input.rs
|
@ -1,29 +1,62 @@
|
||||||
use std::time::Instant;
|
use std::{
|
||||||
|
sync::{Arc, RwLock},
|
||||||
use winit::{
|
time::Instant,
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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::{
|
use super::{
|
||||||
common::{ImguiState, UiExt as _, WindowState, WindowStateBuilder},
|
common::{ImguiState, UiExt, WindowState, WindowStateBuilder},
|
||||||
AppWindow, UserEvent,
|
AppWindow, UserEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct InputWindow {
|
pub struct InputWindow {
|
||||||
window: WindowState,
|
window: WindowState,
|
||||||
imgui: Option<ImguiState>,
|
imgui: Option<ImguiState>,
|
||||||
|
input_mapper: Arc<RwLock<InputMapper>>,
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
|
now_binding: Option<VBKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl InputWindow {
|
||||||
pub fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<UserEvent>) -> Self {
|
pub fn new(
|
||||||
|
event_loop: &ActiveEventLoop,
|
||||||
|
input_mapper: Arc<RwLock<InputMapper>>,
|
||||||
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
|
) -> Self {
|
||||||
let window = WindowStateBuilder::new(event_loop)
|
let window = WindowStateBuilder::new(event_loop)
|
||||||
.with_title("Map Inputs")
|
.with_title("Bind Inputs")
|
||||||
|
.with_inner_size(LogicalSize::new(600, 400))
|
||||||
.build();
|
.build();
|
||||||
Self {
|
Self {
|
||||||
window,
|
window,
|
||||||
imgui: None,
|
imgui: None,
|
||||||
|
input_mapper,
|
||||||
|
now_binding: None,
|
||||||
proxy,
|
proxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,17 +83,50 @@ impl InputWindow {
|
||||||
.expect("Failed to prepare frame");
|
.expect("Failed to prepare frame");
|
||||||
let ui = context.new_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) {
|
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_row();
|
||||||
|
|
||||||
ui.table_next_column();
|
for (key, name) in KEY_NAMES {
|
||||||
ui.text("Key");
|
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();
|
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();
|
window.end();
|
||||||
}
|
}
|
||||||
let mut encoder: wgpu::CommandEncoder = window
|
let mut encoder: wgpu::CommandEncoder = window
|
||||||
|
@ -102,6 +168,17 @@ impl InputWindow {
|
||||||
|
|
||||||
frame.present();
|
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 {
|
impl AppWindow for InputWindow {
|
||||||
|
@ -111,6 +188,7 @@ impl AppWindow for InputWindow {
|
||||||
|
|
||||||
fn init(&mut self) {
|
fn init(&mut self) {
|
||||||
self.imgui = Some(ImguiState::new(&self.window));
|
self.imgui = Some(ImguiState::new(&self.window));
|
||||||
|
self.window.window.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event<UserEvent>) {
|
fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event<UserEvent>) {
|
||||||
|
@ -121,6 +199,7 @@ impl AppWindow for InputWindow {
|
||||||
.proxy
|
.proxy
|
||||||
.send_event(UserEvent::CloseWindow(self.id()))
|
.send_event(UserEvent::CloseWindow(self.id()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
WindowEvent::KeyboardInput { event, .. } => self.try_bind_key(event),
|
||||||
WindowEvent::RedrawRequested => self.draw(),
|
WindowEvent::RedrawRequested => self.draw(),
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
use winit::{
|
use std::sync::{Arc, RwLock};
|
||||||
event::{ElementState, KeyEvent},
|
|
||||||
keyboard::{Key, NamedKey},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::shrooms_vb_core::VBKey;
|
use winit::event::{ElementState, KeyEvent};
|
||||||
|
|
||||||
|
use crate::{input::InputMapper, shrooms_vb_core::VBKey};
|
||||||
|
|
||||||
pub struct ControllerState {
|
pub struct ControllerState {
|
||||||
|
input_mapper: Arc<RwLock<InputMapper>>,
|
||||||
pressed: VBKey,
|
pressed: VBKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControllerState {
|
impl ControllerState {
|
||||||
pub fn new() -> Self {
|
pub fn new(input_mapper: Arc<RwLock<InputMapper>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
input_mapper,
|
||||||
pressed: VBKey::SGN,
|
pressed: VBKey::SGN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +22,7 @@ impl ControllerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn key_event(&mut self, event: &KeyEvent) -> bool {
|
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;
|
return false;
|
||||||
};
|
};
|
||||||
match event.state {
|
match event.state {
|
||||||
|
@ -42,23 +43,8 @@ impl ControllerState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_to_input(&self, key: &Key) -> Option<VBKey> {
|
fn key_event_to_input(&self, event: &KeyEvent) -> Option<VBKey> {
|
||||||
match key.as_ref() {
|
let mapper = self.input_mapper.read().unwrap();
|
||||||
Key::Character("a") => Some(VBKey::SEL),
|
mapper.key_event(event)
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<VBKey, Key>,
|
||||||
|
key_bindings: HashMap<Key, VBKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<VBKey, String> {
|
||||||
|
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<VBKey> {
|
||||||
|
self.key_bindings
|
||||||
|
.get(&event.key_without_modifiers())
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ mod app;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod controller;
|
mod controller;
|
||||||
mod emulator;
|
mod emulator;
|
||||||
|
mod input;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
mod shrooms_vb_core;
|
mod shrooms_vb_core;
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ enum VBDataType {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[repr(transparent)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct VBKey: u16 {
|
pub struct VBKey: u16 {
|
||||||
const PWR = 0x0001;
|
const PWR = 0x0001;
|
||||||
const SGN = 0x0002;
|
const SGN = 0x0002;
|
||||||
|
|
Loading…
Reference in New Issue