Skip to content

Commit

Permalink
Add controller
Browse files Browse the repository at this point in the history
  • Loading branch information
aelred committed Oct 9, 2024
1 parent b5bcc1a commit 019a01a
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 14 deletions.
150 changes: 150 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in library 'nes-rust'",
"cargo": {
"args": [
"test",
"--no-run",
"--lib",
"--package=nes-rust"
],
"filter": {
"name": "nes-rust",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Donkey Kong",
"cargo": {
"args": [
"build",
"--bin=nes-rust",
"--package=nes-rust"
],
"filter": {
"name": "nes-rust",
"kind": "bin"
}
},
"args": [],
"stdio": [
"roms/Donkey Kong/Donkey Kong (World) (Rev 1).nes",
null
],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Super Mario Bros.",
"cargo": {
"args": [
"build",
"--bin=nes-rust",
"--package=nes-rust"
],
"filter": {
"name": "nes-rust",
"kind": "bin"
}
},
"args": [],
"stdio": [
"roms/Mario/Super Mario Bros. (World).nes",
null
],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Alwa's Awakening Demo",
"cargo": {
"args": [
"build",
"--bin=nes-rust",
"--package=nes-rust"
],
"filter": {
"name": "nes-rust",
"kind": "bin"
}
},
"args": [],
"stdio": [
"roms/AlwasAwakening_demo.nes",
null
],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'nes-rust'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=nes-rust",
"--package=nes-rust"
],
"filter": {
"name": "nes-rust",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug integration test 'external_tests'",
"cargo": {
"args": [
"test",
"--no-run",
"--test=external_tests",
"--package=nes-rust"
],
"filter": {
"name": "external_tests",
"kind": "test"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug integration test 'simple_programs'",
"cargo": {
"args": [
"test",
"--no-run",
"--test=simple_programs",
"--package=nes-rust"
],
"filter": {
"name": "simple_programs",
"kind": "test"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"files.exclude": {
".idea": true,
"*.iml": true,
"target": true
}
}
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ web-sys = { version = "0.3.70", optional = true, features = [
'DataTransferItem',
'DataTransferItemList',
'File',
# Features required for the mobile controller
'PointerEvent',
# Feature required for saving the game
'Storage',
] }
Expand Down
2 changes: 1 addition & 1 deletion src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl Input for Controller {
}

bitflags! {
#[derive(Default, Debug)]
#[derive(Default, Debug, Copy, Clone)]
pub struct Buttons: u8 {
const A = 0b1000_0000;
const B = 0b0100_0000;
Expand Down
72 changes: 63 additions & 9 deletions src/runtime/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use std::{
use wasm_bindgen::{convert::FromWasmAbi, prelude::*, Clamped};
use web_sys::{
js_sys::{ArrayBuffer, Uint8Array},
CanvasRenderingContext2d, DragEvent, HtmlCanvasElement, ImageData, KeyboardEvent, Storage,
Window,
CanvasRenderingContext2d, Document, DragEvent, EventTarget, HtmlCanvasElement, ImageData,
KeyboardEvent, PointerEvent, Storage, Window,
};
use zip::ZipArchive;

Expand All @@ -28,6 +28,8 @@ impl Runtime for Web {
}

fn run() -> Result<(), Box<dyn Error>> {
let window = window()?;
let dom = dom()?;
let base_ctx = Rc::new(RefCell::new(Option::<NesContext>::None));

if let Some(rom) = load_rom()? {
Expand All @@ -36,7 +38,7 @@ impl Runtime for Web {
}

let ctx = base_ctx.clone();
add_event_listener("keydown", move |event: KeyboardEvent| {
add_event_listener(&window, "keydown", move |event: KeyboardEvent| {
let mut ctx = ctx.borrow_mut();
let nes = match &mut *ctx {
Some(ctx) => &mut ctx.nes,
Expand All @@ -48,7 +50,7 @@ impl Runtime for Web {
})?;

let ctx = base_ctx.clone();
add_event_listener("keyup", move |event: KeyboardEvent| {
add_event_listener(&window, "keyup", move |event: KeyboardEvent| {
let mut ctx = ctx.borrow_mut();
let nes = match &mut *ctx {
Some(ctx) => &mut ctx.nes,
Expand All @@ -59,8 +61,56 @@ impl Runtime for Web {
Ok(())
})?;

let controller_element = dom
.get_element_by_id("controller")
.context("controller not found")?;

add_event_listener(&controller_element, "contextmenu", |event: PointerEvent| {
event.prevent_default();
Ok(())
})?;

const BUTTONS: [(&str, Buttons); 8] = [
("a", Buttons::A),
("b", Buttons::B),
("start", Buttons::START),
("select", Buttons::SELECT),
("up", Buttons::UP),
("down", Buttons::DOWN),
("left", Buttons::LEFT),
("right", Buttons::RIGHT),
];

for (button_id, button) in BUTTONS.iter() {
let element = dom
.get_element_by_id(button_id)
.context(format!("button not found {}", button_id))?;

let ctx = base_ctx.clone();
add_event_listener(&element, "pointerenter", move |_: PointerEvent| {
let mut ctx = ctx.borrow_mut();
let nes = match &mut *ctx {
Some(ctx) => &mut ctx.nes,
None => return Ok(()),
};
nes.controller().press(*button);
Ok(())
})?;

let ctx = base_ctx.clone();
add_event_listener(&element, "pointerout", move |_: PointerEvent| {
let mut ctx = ctx.borrow_mut();
let nes = match &mut *ctx {
Some(ctx) => &mut ctx.nes,
None => return Ok(()),
};
nes.controller().release(*button);
Ok(())
})?;
}

let ctx = base_ctx.clone();
add_event_listener("drop", move |event: DragEvent| {
add_event_listener(&window, "drop", move |event: DragEvent| {
event.prevent_default();
let items = event.data_transfer().context("No data transfered")?.items();

Expand Down Expand Up @@ -121,7 +171,7 @@ impl Runtime for Web {
Ok(())
})?;

add_event_listener("dragover", move |event: DragEvent| {
add_event_listener(&window, "dragover", move |event: DragEvent| {
event.prevent_default();
Ok(())
})?;
Expand Down Expand Up @@ -213,9 +263,12 @@ fn window() -> anyhow::Result<Window> {
web_sys::window().context("no global `window` exists")
}

fn dom() -> anyhow::Result<Document> {
window()?.document().context("DOM not found")
}

fn canvas_context() -> anyhow::Result<CanvasRenderingContext2d> {
let dom = window()?.document().context("DOM not found")?;
let canvas = dom
let canvas = dom()?
.get_element_by_id("canvas")
.context("canvas not found")?;
let canvas: HtmlCanvasElement = canvas
Expand All @@ -237,11 +290,12 @@ fn request_animation_frame(f: &Closure<dyn FnMut(f64)>) -> anyhow::Result<i32> {
}

fn add_event_listener<T: FromWasmAbi + 'static>(
target: &EventTarget,
event: &str,
listener: impl FnMut(T) -> Result<(), Box<dyn Error>> + 'static,
) -> anyhow::Result<()> {
let closure = closure(listener);
window()?
target
.add_event_listener_with_callback(event, closure.as_ref().unchecked_ref())
.map_err(|_| anyhow!("failed to add event listener"))?;
// Make closure live forever
Expand Down
Loading

0 comments on commit 019a01a

Please sign in to comment.