Skip to content

Commit

Permalink
Support drag+drop ROMs
Browse files Browse the repository at this point in the history
  • Loading branch information
aelred committed Sep 22, 2024
1 parent 9127c11 commit a625e77
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 7 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ web-sys = { version = "0.3.70", optional = true, features = [
'Element',
'HtmlCanvasElement',
'Window',
# Features required for reading drag+dropped ROMs
'DragEvent',
'DataTransfer',
'DataTransferItem',
'DataTransferItemList',
'File',
] }
console_log = { version = "1.0.0", optional = true }

Expand Down
77 changes: 71 additions & 6 deletions src/runtime/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
use crate::{runtime::Runtime, BufferDisplay, Buttons, INes, HEIGHT, NES, WIDTH};
use anyhow::{anyhow, Context};
use std::{cell::RefCell, error::Error, rc::Rc};
use wasm_bindgen::{prelude::*, Clamped};
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, KeyboardEvent, Window};
use wasm_bindgen::{convert::FromWasmAbi, prelude::*, Clamped};
use web_sys::{
js_sys::{ArrayBuffer, Uint8Array},
CanvasRenderingContext2d, DragEvent, HtmlCanvasElement, ImageData, KeyboardEvent, Window,
};

const ROM: &[u8] = include_bytes!("../../roms/AlwasAwakening_demo.nes");
const MS_PER_FRAME: f64 = 1000.0 / 60.0;
Expand All @@ -12,7 +15,8 @@ pub struct Web;

impl Runtime for Web {
fn run() -> Result<(), Box<dyn Error>> {
console_log::init_with_level(log::Level::Debug).expect("Failed to initialize logger");
console_log::init_with_level(log::Level::Debug)
.map_err(|_| anyhow!("Failed to initialize logger"))?;

let ines = INes::read(ROM)?;
let cartridge = ines.into_cartridge();
Expand All @@ -23,12 +27,63 @@ impl Runtime for Web {
add_event_listener("keydown", move |event: KeyboardEvent| {
let button = keycode_binding(&event.code());
nesdown.borrow_mut().controller().press(button);
Ok(())
})?;

let nesup = nes.clone();
add_event_listener("keyup", move |event: KeyboardEvent| {
let button = keycode_binding(&event.code());
nesup.borrow_mut().controller().release(button);
Ok(())
})?;

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

for i in 0..items.length() {
let item = items.get(i).context("No data transfer item found")?;
if let Some(file) = item
.get_as_file()
.map_err(|_| anyhow!("Failed to get file"))?
{
let nesread = nesdrop.clone();

let success = closure(move |array_buffer: JsValue| {
let array_buffer = array_buffer
.dyn_into::<ArrayBuffer>()
.map_err(|_| anyhow!("Failed to convert to ArrayBuffer"))?;

let array = Uint8Array::new(&array_buffer);
let mut rom = vec![0; array.length() as usize];
array.copy_to(&mut rom);

let ines = INes::read(&mut rom.as_slice())?;
let cartridge = ines.into_cartridge();
let display = BufferDisplay::default();
let nes_new = NES::new(cartridge, display);

nesread.replace(nes_new);
Ok(())
});
let failure = closure(move |_| {
log::error!("An error occurred getting array buffer");
Ok(())
});

let _ = file.array_buffer().then2(&success, &failure);
success.forget();
failure.forget();
}
}

Ok(())
})?;

add_event_listener("dragover", move |event: DragEvent| {
event.prevent_default();
Ok(())
})?;

let context = canvas_context()?;
Expand Down Expand Up @@ -115,15 +170,25 @@ fn request_animation_frame(f: &Closure<dyn FnMut(f64)>) -> anyhow::Result<i32> {
.map_err(|_| anyhow!("failed to request animation frame"))
}

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

fn closure<T: FromWasmAbi + 'static>(
mut function: impl FnMut(T) -> Result<(), Box<dyn Error>> + 'static,
) -> Closure<dyn FnMut(T)> {
Closure::<dyn FnMut(T)>::new(move |arg| {
if let Err(err) = function(arg) {
log::error!("Error: {}", err);
}
})
}
2 changes: 1 addition & 1 deletion web/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default {
new WasmPackPlugin({
crateDirectory: resolve(__dirname, '..'),
outDir: resolve(__dirname, 'pkg'),
forceMode: 'production',
// forceMode: 'production',
extraArgs: '--no-default-features --features=web',
}),
],
Expand Down

0 comments on commit a625e77

Please sign in to comment.