From 1a830a3d1b9d99653322d5ae49ea8165de7ed9d0 Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Mon, 28 Oct 2024 20:25:01 +1300 Subject: Rewrite emulator This is a complete rewrite and restructure of the entire emulator project, as part of the effort in locking down the Bedrock specification and in creating much better tooling for creating and using Bedrock programs. This commit adds a command-line argument scheme, an embedded assembler, a headless emulator for use in non-graphical environments, deferred window creation for programs that do not access the screen device, and new versions of phosphor and bedrock-core. The new version of phosphor supports multi-window programs, which will make it possible to implement program forking in the system device later on, and the new version of bedrock-core implements the final core specification. --- src/devices/input_device.rs | 233 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 src/devices/input_device.rs (limited to 'src/devices/input_device.rs') diff --git a/src/devices/input_device.rs b/src/devices/input_device.rs new file mode 100644 index 0000000..638c277 --- /dev/null +++ b/src/devices/input_device.rs @@ -0,0 +1,233 @@ +use crate::*; +use bedrock_core::*; +use phosphor::*; + +use std::collections::VecDeque; + +macro_rules! fn_on_scroll { + ($fn_name:ident($value:ident, $delta:ident)) => { + pub fn $fn_name(&mut self, delta: f32) { + self.$delta += delta; + while self.$delta >= 1.0 { + self.$value = self.$value.saturating_add(1); + self.$delta -= 1.0; + self.wake = true; + } + while self.$delta <= -1.0 { + self.$value = self.$value.saturating_sub(1); + self.$delta += 1.0; + self.wake = true; + } + } + }; +} + + +pub struct InputDevice { + pub wake: bool, + pub accessed: bool, + + pub pointer_active: bool, + pub pointer_buttons: u8, + pub position: ScreenPosition, + pub x_read: u16, + pub y_read: u16, + + pub h_scroll: i8, + pub v_scroll: i8, + pub h_scroll_delta: f32, + pub v_scroll_delta: f32, + + pub keyboard_active: bool, + pub characters: VecDeque, + pub navigation: u8, + pub modifiers: u8, + + pub gamepad_1: u8, + pub gamepad_2: u8, + pub gamepad_3: u8, + pub gamepad_4: u8, +} + +impl InputDevice { + pub fn new() -> Self { + Self { + wake: false, + accessed: false, + + pointer_active: false, + pointer_buttons: 0, + + position: ScreenPosition::ZERO, + x_read: 0, + y_read: 0, + + h_scroll: 0, + v_scroll: 0, + h_scroll_delta: 0.0, + v_scroll_delta: 0.0, + + keyboard_active: true, + characters: VecDeque::new(), + modifiers: 0, + navigation: 0, + + gamepad_1: 0, + gamepad_2: 0, + gamepad_3: 0, + gamepad_4: 0, + } + } + + pub fn on_cursor_enter(&mut self) { + self.pointer_active = true; + self.wake = true; + } + + pub fn on_cursor_exit(&mut self) { + self.pointer_active = false; + self.wake = true; + } + + pub fn on_cursor_move(&mut self, position: Position) { + let screen_position = ScreenPosition { + x: position.x as i16 as u16, + y: position.y as i16 as u16, + }; + if self.position != screen_position { + self.position = screen_position; + self.wake = true; + } + } + + pub fn on_mouse_button(&mut self, button: MouseButton, action: Action) { + let mask = match button { + MouseButton::Left => 0x80, + MouseButton::Middle => 0x40, + MouseButton::Right => 0x20, + }; + let pointer_buttons = match action { + Action::Pressed => self.pointer_buttons | mask, + Action::Released => self.pointer_buttons & !mask, + }; + if self.pointer_buttons != pointer_buttons { + self.pointer_buttons = pointer_buttons; + self.wake = true; + } + } + + fn_on_scroll!(on_horizontal_scroll(h_scroll, h_scroll_delta)); + fn_on_scroll!(on_vertical_scroll(v_scroll, v_scroll_delta)); + + pub fn read_horizontal_scroll(&mut self) -> u8 { + std::mem::take(&mut self.h_scroll) as u8 + } + + pub fn read_vertical_scroll(&mut self) -> u8 { + std::mem::take(&mut self.v_scroll) as u8 + } + + pub fn on_character(&mut self, character: char) { + let character = match character { + '\r' => '\n', + _ => character, + }; + let mut bytes = [0; 4]; + let string = character.encode_utf8(&mut bytes); + for byte in string.bytes() { + self.characters.push_back(byte); + } + self.wake = true; + } + + pub fn on_keypress(&mut self, key: KeyCode, action: Action) { + let shift = self.modifiers & 0x40 != 0; + let mask = match key { + KeyCode::ArrowUp => 0x80, // up + KeyCode::ArrowDown => 0x40, // down + KeyCode::ArrowLeft => 0x20, // left + KeyCode::ArrowRight => 0x10, // right + KeyCode::Enter => 0x08, // confirm + KeyCode::Escape => 0x04, // cancel + KeyCode::Tab => match shift { // shift + false => 0x02, // next + true => 0x01 // previous + }, + _ => return, + }; + let navigation = match action { + Action::Pressed => self.navigation | mask, + Action::Released => self.navigation & !mask, + }; + if self.navigation != navigation { + self.navigation = navigation; + self.wake = true; + } + } + + pub fn on_modifier(&mut self, state: ModifiersState) { + let mut modifiers = 0; + if state.control_key() { modifiers |= 0x80 } + if state.shift_key() { modifiers |= 0x40 } + if state.alt_key() { modifiers |= 0x20 } + if state.super_key() { modifiers |= 0x10 } + if self.modifiers != modifiers { + self.modifiers = modifiers; + self.wake = true; + } + } +} + +impl Device for InputDevice { + fn read(&mut self, port: u8) -> u8 { + self.accessed = true; + match port { + 0x0 => read_b!(self.pointer_active), + 0x1 => self.pointer_buttons, + 0x2 => self.read_horizontal_scroll(), + 0x3 => self.read_vertical_scroll(), + 0x4 => { self.x_read = self.position.x as u16; read_h!(self.x_read) }, + 0x5 => read_l!(self.position.x), + 0x6 => { self.y_read = self.position.y as u16; read_h!(self.y_read) }, + 0x7 => read_l!(self.position.y), + 0x8 => read_b!(self.keyboard_active), + 0x9 => self.characters.pop_front().unwrap_or(0), + 0xa => self.navigation, + 0xb => self.modifiers, + 0xc => self.gamepad_1, + 0xd => self.gamepad_2, + 0xe => self.gamepad_3, + 0xf => self.gamepad_4, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, _value: u8) -> Option { + self.accessed = true; + match port { + 0x0 => (), + 0x1 => (), + 0x2 => (), + 0x3 => (), + 0x4 => (), + 0x5 => (), + 0x6 => (), + 0x7 => (), + 0x8 => (), + 0x9 => self.characters.clear(), + 0xa => (), + 0xb => (), + 0xc => (), + 0xd => (), + 0xe => (), + 0xf => (), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } +} -- cgit v1.2.3-70-g09d2