diff --git a/Cargo.toml b/Cargo.toml index fe1ed01..e461a5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/src/runtime/web.rs b/src/runtime/web.rs index c208105..4060cb2 100644 --- a/src/runtime/web.rs +++ b/src/runtime/web.rs @@ -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; @@ -12,7 +15,8 @@ pub struct Web; impl Runtime for Web { fn run() -> Result<(), Box> { - 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(); @@ -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::() + .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()?; @@ -115,11 +170,11 @@ fn request_animation_frame(f: &Closure) -> anyhow::Result { .map_err(|_| anyhow!("failed to request animation frame")) } -fn add_event_listener( +fn add_event_listener( event: &str, - listener: impl FnMut(KeyboardEvent) + 'static, + listener: impl FnMut(T) -> Result<(), Box> + 'static, ) -> anyhow::Result<()> { - let closure = Closure::::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"))?; @@ -127,3 +182,13 @@ fn add_event_listener( closure.forget(); Ok(()) } + +fn closure( + mut function: impl FnMut(T) -> Result<(), Box> + 'static, +) -> Closure { + Closure::::new(move |arg| { + if let Err(err) = function(arg) { + log::error!("Error: {}", err); + } + }) +} diff --git a/web/webpack.config.js b/web/webpack.config.js index 005db7b..4bbdd14 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -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', }), ],