223 lines
6.9 KiB
Rust
223 lines
6.9 KiB
Rust
use std::{
|
|
sync::{Arc, RwLock},
|
|
time::Instant,
|
|
};
|
|
|
|
use winit::{
|
|
dpi::LogicalSize,
|
|
event::{Event, KeyEvent, WindowEvent},
|
|
event_loop::{ActiveEventLoop, EventLoopProxy},
|
|
};
|
|
|
|
use crate::{
|
|
emulator::{SimId, VBKey},
|
|
input::InputMapper,
|
|
};
|
|
|
|
use super::{
|
|
common::{ImguiState, UiExt, WindowState, WindowStateBuilder},
|
|
AppWindow, UserEvent,
|
|
};
|
|
|
|
pub struct InputWindow {
|
|
window: WindowState,
|
|
imgui: ImguiState,
|
|
input_mapper: Arc<RwLock<InputMapper>>,
|
|
proxy: EventLoopProxy<UserEvent>,
|
|
now_binding: Option<(SimId, 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 {
|
|
pub fn new(
|
|
event_loop: &ActiveEventLoop,
|
|
input_mapper: Arc<RwLock<InputMapper>>,
|
|
proxy: EventLoopProxy<UserEvent>,
|
|
) -> Self {
|
|
let window = WindowStateBuilder::new(event_loop)
|
|
.with_title("Bind Inputs")
|
|
.with_inner_size(LogicalSize::new(600, 400))
|
|
.build();
|
|
let imgui = ImguiState::new(&window);
|
|
Self {
|
|
window,
|
|
imgui,
|
|
input_mapper,
|
|
now_binding: None,
|
|
proxy,
|
|
}
|
|
}
|
|
|
|
fn draw(&mut self) {
|
|
let window = &mut self.window;
|
|
let imgui = &mut self.imgui;
|
|
let mut context = imgui.context.lock().unwrap();
|
|
|
|
let now = Instant::now();
|
|
context.io_mut().update_delta_time(now - imgui.last_frame);
|
|
imgui.last_frame = now;
|
|
|
|
let frame = match window.surface.get_current_texture() {
|
|
Ok(frame) => frame,
|
|
Err(e) => {
|
|
if !self.window.minimized {
|
|
eprintln!("dropped frame: {e:?}");
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
imgui
|
|
.platform
|
|
.prepare_frame(context.io_mut(), &window.window)
|
|
.expect("Failed to prepare frame");
|
|
let ui = context.new_frame();
|
|
|
|
let mut render_key_bindings = |sim_id: SimId| {
|
|
if let Some(table) = ui.begin_table("controls", 2) {
|
|
let binding_names = {
|
|
let mapper = self.input_mapper.read().unwrap();
|
|
mapper.binding_names(&sim_id)
|
|
};
|
|
ui.table_next_row();
|
|
|
|
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((sim_id, 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((sim_id, key));
|
|
}
|
|
});
|
|
ui.same_line();
|
|
if ui.button(format!("Clear##{name}")) {
|
|
let mut mapper = self.input_mapper.write().unwrap();
|
|
mapper.clear_binding(sim_id, key);
|
|
}
|
|
}
|
|
|
|
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(SimId::Player1);
|
|
tab.end();
|
|
}
|
|
if let Some(tab) = ui.tab_item("Player 2") {
|
|
render_key_bindings(SimId::Player2);
|
|
tab.end();
|
|
}
|
|
tabs.end();
|
|
}
|
|
window.end();
|
|
}
|
|
let mut encoder: wgpu::CommandEncoder = window
|
|
.device
|
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
|
|
|
if imgui.last_cursor != ui.mouse_cursor() {
|
|
imgui.last_cursor = ui.mouse_cursor();
|
|
imgui.platform.prepare_render(ui, &window.window);
|
|
}
|
|
|
|
let view = frame
|
|
.texture
|
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
label: None,
|
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
view: &view,
|
|
resolve_target: None,
|
|
ops: wgpu::Operations {
|
|
load: wgpu::LoadOp::Clear(imgui.clear_color),
|
|
store: wgpu::StoreOp::Store,
|
|
},
|
|
})],
|
|
depth_stencil_attachment: None,
|
|
timestamp_writes: None,
|
|
occlusion_query_set: None,
|
|
});
|
|
|
|
// Draw the game
|
|
imgui
|
|
.renderer
|
|
.render(context.render(), &window.queue, &window.device, &mut rpass)
|
|
.expect("Rendering failed");
|
|
|
|
drop(rpass);
|
|
|
|
window.queue.submit(Some(encoder.finish()));
|
|
|
|
frame.present();
|
|
}
|
|
|
|
fn try_bind_key(&mut self, event: &KeyEvent) {
|
|
if !event.state.is_pressed() {
|
|
return;
|
|
}
|
|
let Some((sim_id, vb)) = self.now_binding.take() else {
|
|
return;
|
|
};
|
|
let mut mapper = self.input_mapper.write().unwrap();
|
|
mapper.bind_key(sim_id, vb, event.physical_key);
|
|
}
|
|
}
|
|
|
|
impl AppWindow for InputWindow {
|
|
fn id(&self) -> winit::window::WindowId {
|
|
self.window.window.id()
|
|
}
|
|
|
|
fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event<UserEvent>) {
|
|
match event {
|
|
Event::WindowEvent { event, .. } => match event {
|
|
WindowEvent::Resized(size) => self.window.handle_resize(size),
|
|
WindowEvent::CloseRequested => {
|
|
self.proxy.send_event(UserEvent::Close(self.id())).unwrap()
|
|
}
|
|
WindowEvent::KeyboardInput { event, .. } => self.try_bind_key(event),
|
|
WindowEvent::RedrawRequested => self.draw(),
|
|
_ => (),
|
|
},
|
|
Event::AboutToWait => {
|
|
self.window.window.request_redraw();
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
let window = &self.window;
|
|
let imgui = &mut self.imgui;
|
|
let mut context = imgui.context.lock().unwrap();
|
|
imgui
|
|
.platform
|
|
.handle_event(context.io_mut(), &window.window, event);
|
|
}
|
|
}
|