From 560f1d969374cfb7842ca737cb53d44c6b0a0e41 Mon Sep 17 00:00:00 2001 From: Alex Janka <alex@alexjanka.com> Date: Mon, 27 Nov 2023 12:58:13 +1100 Subject: [PATCH] keyboard input works on mac gui (LMAO) --- Cargo.lock | 2 +- gui/src/macos/cacao_window_manager.rs | 164 ++++++++++++++++++++---- gui/src/macos/mod.rs | 7 +- lib/src/processor/memory/mmio/joypad.rs | 1 + 4 files changed, 146 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0b04e1..edb91a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cacao" version = "0.4.0-beta2" -source = "git+https://github.com/italicsjenga/cacao#6144e9f244abfd15687de00b6cfda6b4606f351a" +source = "git+https://github.com/italicsjenga/cacao#4fd93e3faeec5e80aef36779745b3506c9b5017d" dependencies = [ "bitmask-enum", "block2 0.2.0-alpha.6", diff --git a/gui/src/macos/cacao_window_manager.rs b/gui/src/macos/cacao_window_manager.rs index 62d2d20..bd12f66 100644 --- a/gui/src/macos/cacao_window_manager.rs +++ b/gui/src/macos/cacao_window_manager.rs @@ -9,13 +9,16 @@ use std::{ }; use cacao::{ - appkit::window::{Window, WindowConfig, WindowDelegate, WindowStyle}, + appkit::{ + window::{Window, WindowConfig, WindowDelegate, WindowStyle}, + Event, EventMask, EventMonitor, + }, core_foundation::base::TCFTypeRef, }; use cpal::Stream; use frontend_common::window::{RendererChannel, WindowManager}; use gb_emu_lib::{ - connect::{EmulatorMessage, RendererMessage, ResolutionData}, + connect::{EmulatorMessage, JoypadButtons, JoypadState, RendererMessage, ResolutionData}, renderer::{RendererBackend, RendererBackendManager, WindowOptions}, }; use raw_window_handle::{ @@ -27,8 +30,15 @@ use uuid::Uuid; use super::{dispatch, AppMessage}; pub enum EmuWindowMessage { - Closing, - Renderer(RendererMessage<[u8; 4]>), + Closing { + id: Uuid, + }, + Renderer { + id: Uuid, + renderer_message: RendererMessage<[u8; 4]>, + }, + KeyDown(JoypadButtons), + KeyUp(JoypadButtons), } pub struct EmulatorHandles { @@ -60,18 +70,101 @@ impl Drop for EmulatorHandles { } } +struct ButtonHandler { + _down_monitor: EventMonitor, + _up_monitor: EventMonitor, +} + +impl ButtonHandler { + fn new() -> Self { + let _down_monitor = Event::local_monitor(EventMask::KeyDown, |v| { + if let Some(button) = get_buttons(&v) { + dispatch(AppMessage::EmuWindow(EmuWindowMessage::KeyDown(button))); + None + } else { + Some(v) + } + }); + let _up_monitor = Event::local_monitor(EventMask::KeyUp, |v| { + if let Some(button) = get_buttons(&v) { + dispatch(AppMessage::EmuWindow(EmuWindowMessage::KeyUp(button))); + None + } else { + Some(v) + } + }); + + Self { + _down_monitor, + _up_monitor, + } + } +} + +trait GetKeyChar { + fn get_key_char(&self) -> char; +} + +impl GetKeyChar for JoypadButtons { + fn get_key_char(&self) -> char { + match self { + JoypadButtons::Down => 's', + JoypadButtons::Up => 'w', + JoypadButtons::Left => 'a', + JoypadButtons::Right => 'd', + JoypadButtons::Start => '=', + JoypadButtons::Select => '-', + JoypadButtons::B => ';', + JoypadButtons::A => '\'', + } + } +} + +const ALL_BUTTONS: [JoypadButtons; 8] = [ + JoypadButtons::Down, + JoypadButtons::Up, + JoypadButtons::Left, + JoypadButtons::Right, + JoypadButtons::Start, + JoypadButtons::Select, + JoypadButtons::B, + JoypadButtons::A, +]; + +fn get_buttons(event: &Event) -> Option<JoypadButtons> { + let characters = event.characters(); + if characters.len() != 1 { + panic!("ok that assumption was wrong lol. event characters CAN be != 1"); + } + + if event.current_modifier_flags().is_empty() { + for button in ALL_BUTTONS { + if characters.contains(button.get_key_char()) { + return Some(button); + } + } + } + None +} + pub struct CacaoWindowManager { backend_manager: Arc<RendererBackendManager>, windows: HashMap<Uuid, Window<CacaoWindow>>, handles: Option<EmulatorHandles>, + joypad_state: JoypadState, + _button_handler: ButtonHandler, } impl CacaoWindowManager { pub fn new(display_handle: RawDisplayHandle) -> Self { + let _button_handler = ButtonHandler::new(); + Self { backend_manager: Arc::new(RendererBackendManager::new(display_handle)), windows: HashMap::new(), handles: None, + joypad_state: Default::default(), + _button_handler, } } @@ -89,19 +182,22 @@ impl CacaoWindowManager { } } - pub fn message(&mut self, id: Uuid, message: EmuWindowMessage) { + pub fn message(&mut self, message: EmuWindowMessage) { match message { - EmuWindowMessage::Closing => { + EmuWindowMessage::Closing { id } => { self.windows.remove(&id); if self.windows.is_empty() { let _ = self.handles.take(); } } - EmuWindowMessage::Renderer(message) => { + EmuWindowMessage::Renderer { + id, + renderer_message, + } => { if let Some(window) = self.windows.get_mut(&id) { if let Some(delegate) = window.delegate.as_mut() { delegate.message( - message, + renderer_message, Window { delegate: None, objc: window.objc.clone(), @@ -110,6 +206,30 @@ impl CacaoWindowManager { } } } + EmuWindowMessage::KeyDown(key) => { + if let Some(handles) = self.handles.as_ref() { + let old_state = self.joypad_state; + self.joypad_state.set(key, true); + if self.joypad_state != old_state { + handles + .sender + .send(EmulatorMessage::JoypadUpdate(self.joypad_state)) + .unwrap(); + } + } + } + EmuWindowMessage::KeyUp(key) => { + if let Some(handles) = self.handles.as_ref() { + let old_state = self.joypad_state; + self.joypad_state.set(key, false); + if self.joypad_state != old_state { + handles + .sender + .send(EmulatorMessage::JoypadUpdate(self.joypad_state)) + .unwrap(); + } + } + } } } } @@ -172,17 +292,18 @@ enum MonitorThreadState { impl MonitorThread { fn new(receiver: Receiver<RendererMessage<[u8; 4]>>, id: Uuid) -> Self { let (destroy, destroy_recv) = mpsc::channel(); - let thread = std::thread::spawn(move || loop { - if let Ok(message) = receiver.recv() { - dispatch(AppMessage::EmuWindow { - id, - message: EmuWindowMessage::Renderer(message), - }) - } - if let Ok(true) = destroy_recv.try_recv() { - return; - } - }); + let thread = + std::thread::spawn(move || loop { + if let Ok(renderer_message) = receiver.recv() { + dispatch(AppMessage::EmuWindow(EmuWindowMessage::Renderer { + id, + renderer_message, + })); + } + if let Ok(true) = destroy_recv.try_recv() { + return; + } + }); Self { state: MonitorThreadState::Live { thread, destroy }, } @@ -292,10 +413,7 @@ impl WindowDelegate for CacaoWindow { } fn will_close(&self) { - dispatch(AppMessage::EmuWindow { - id: self.id, - message: EmuWindowMessage::Closing, - }) + dispatch(AppMessage::EmuWindow(EmuWindowMessage::Closing { id: self.id })); } } diff --git a/gui/src/macos/mod.rs b/gui/src/macos/mod.rs index 818bf5f..d7aca06 100644 --- a/gui/src/macos/mod.rs +++ b/gui/src/macos/mod.rs @@ -10,7 +10,6 @@ use cacao::notification_center::Dispatcher; use frontend_common::audio; use gb_emu_lib::connect::{EmulatorCoreTrait, EmulatorMessage}; use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; -use uuid::Uuid; use self::cacao_window_manager::{CacaoWindowManager, EmuWindowMessage}; use self::preferences::{PreferencesMessage, PreferencesUi}; @@ -21,7 +20,7 @@ mod preferences; pub(crate) enum AppMessage { Core(CoreMessage), Preferences(PreferencesMessage), - EmuWindow { id: Uuid, message: EmuWindowMessage }, + EmuWindow(EmuWindowMessage), } pub(crate) enum CoreMessage { @@ -122,9 +121,9 @@ impl Dispatcher for TwincUiApp { } } } - AppMessage::EmuWindow { id, message } => { + AppMessage::EmuWindow(window_message) => { if let Ok(mut window_manager) = self.current_game.write() { - window_manager.message(id, message); + window_manager.message(window_message); } } } diff --git a/lib/src/processor/memory/mmio/joypad.rs b/lib/src/processor/memory/mmio/joypad.rs index 729509b..d9af209 100644 --- a/lib/src/processor/memory/mmio/joypad.rs +++ b/lib/src/processor/memory/mmio/joypad.rs @@ -22,6 +22,7 @@ pub struct JoypadState { pub a: bool, } +#[derive(Debug, Clone, Copy, PartialEq)] pub enum JoypadButtons { Down, Up,