wasm filedialog

This commit is contained in:
kirjavascript 2023-03-13 00:35:02 +00:00
parent 14093014af
commit 89c460c184
8 changed files with 275 additions and 87 deletions

View file

@ -1,11 +1,11 @@
[package]
name = "frontend"
default-run = "trueLMAO"
default-run = "truelmao"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "trueLMAO"
name = "truelmao"
path = "src/main.rs"
[lib]
@ -15,11 +15,11 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
emu = { path = "../emu" }
# rfd = "0.8"
# hqx = { git = "https://github.com/CryZe/wasmboy-rs", branch = "master" }
egui = "0.21.0"
eframe = { version = "0.21.0", features = ["persistence"] }
rfd = "0.11"
# serde = { version = "1", features = ["derive"] } # You only need this if you want app persistence
# native:
@ -31,3 +31,6 @@ tracing-subscriber = "0.3"
console_error_panic_hook = "0.1.6"
tracing-wasm = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3.58" }
wasm-bindgen = "=0.2.84"
js-sys = "0.3"

View file

@ -1,5 +1,6 @@
use emu::Megadrive;
use crate::widgets;
use widgets::file::FileDialog;
use std::collections::VecDeque;
pub struct App {
@ -9,6 +10,7 @@ pub struct App {
pub vsync: bool,
pub running: bool,
test_vec: VecDeque<u64>,
file: FileDialog,
}
impl Default for App {
@ -21,6 +23,7 @@ impl Default for App {
vsync: false,
running: true,
test_vec: VecDeque::with_capacity(60),
file: Default::default(),
}
}
}
@ -87,7 +90,7 @@ impl eframe::App for App {
// debug stuff
self.debug.render(&ctx, &self.emu);
self.debug.render(&ctx, &mut self.emu);
egui::CentralPanel::default().show(ctx, |ui| {
egui::warn_if_debug_build(ui);
@ -107,6 +110,10 @@ impl eframe::App for App {
if self.test_vec.len() > 60 {
self.test_vec.pop_front();
}
if ui.button("Open file…").clicked() {
self.file.open_file();
}
});
}

View file

@ -0,0 +1,69 @@
pub struct Memory {
tab_index: usize,
}
impl Default for Memory {
fn default() -> Self {
Self {
tab_index: 0,
}
}
}
const ASCII: &str = r##"................................ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~................................. ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"##;
impl Memory {
pub fn render(&mut self, ctx: &egui::Context, emu: &emu::Megadrive) {
egui::Window::new("memory")
.min_width(600.)
.show(ctx, |ui| {
let tabs: [(&str, usize, Box<dyn Fn(usize) -> u8>); 4] = [
("68K RAM", 0x10000,
Box::new(|offset: usize| emu.core.mem.ram[offset])),
("Z80 RAM", 0x2000,
Box::new(|offset: usize| emu.core.mem.z80.read_byte(offset as _))),
("ROM", emu.core.mem.rom.size(),
Box::new(|offset: usize| emu.core.mem.rom.read_byte(offset as _))),
("IO", 0x20,
Box::new(|offset: usize| emu.core.mem.io.read_byte(offset as _))),
];
let (selected_name, total_bytes, accessor) = &tabs[self.tab_index];
ui.horizontal(|ui| {
for (i, (name, _, _)) in tabs.iter().enumerate() {
if ui
.selectable_label(selected_name == name, *name)
.clicked()
{
self.tab_index = i;
}
}
});
let bytes_row = 16;
let rows = total_bytes / bytes_row;
egui::ScrollArea::vertical()
.auto_shrink([true, false])
.always_show_scroll(true)
.show_rows(ui, 8., rows, |ui, row_range| {
for i in row_range {
let offset = i * bytes_row;
let bytes = (offset..offset+bytes_row)
.map(|offset| {
format!(" {:02X}", accessor(offset))
}).collect::<String>();
let ascii = (offset..offset+bytes_row)
.map(|offset| {
format!("{}", ASCII.chars().nth(accessor(offset) as _).unwrap_or('.'))
}).collect::<String>();
ui.monospace(format!("{:06X} {} {}", i * 16, bytes, ascii));
}
});
});
}
}

View file

@ -1,82 +1,28 @@
pub mod palette;
pub mod vram;
pub mod cpu;
pub mod memory;
use vram::VRAM;
pub struct Debug {
pub vram: VRAM,
tab_index: usize,
pub vram: vram::VRAM,
pub memory: memory::Memory,
}
impl Default for Debug {
fn default() -> Self {
Self {
vram: Default::default(),
tab_index: 0,
memory: Default::default(),
}
}
}
const ASCII: &str = r##"................................ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~................................. ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"##;
impl Debug {
pub fn render(&mut self, ctx: &egui::Context, emu: &emu::Megadrive) {
pub fn render(&mut self, ctx: &egui::Context, emu: &mut emu::Megadrive) {
cpu::cpu_window(&ctx, &emu);
palette::palette_window(&ctx, &emu);
palette::palette_window(&ctx, emu);
self.vram.render(&ctx, &emu);
egui::Window::new("memory")
.show(ctx, |ui| {
let tabs: [(&str, usize, Box<dyn Fn(usize) -> u8>); 4] = [
("68K RAM", 0x10000, Box::new(|offset: usize|
emu.core.mem.ram[offset])),
("Z80 RAM", 0x2000, Box::new(|offset: usize|
emu.core.mem.z80.read_byte(offset as _))),
("ROM", emu.core.mem.rom.size(), Box::new(|offset: usize|
emu.core.mem.rom.read_byte(offset as _))),
("IO", 0x20, Box::new(|offset: usize|
emu.core.mem.io.read_byte(offset as _))),
];
let (selected_name, total_bytes, accessor) = &tabs[self.tab_index];
ui.horizontal(|ui| {
for (i, (name, _, _)) in tabs.iter().enumerate() {
if ui
.selectable_label(selected_name == name, *name)
.clicked()
{
self.tab_index = i;
}
}
});
let bytes_row = 16;
let rows = total_bytes / bytes_row;
egui::ScrollArea::vertical()
.max_height(512.)
.show_rows(ui, 8., rows, |ui, row_range| {
for i in row_range {
let offset = i * bytes_row;
let bytes = (offset..offset+bytes_row)
.map(|offset| {
format!(" {:02X}", accessor(offset))
}).collect::<String>();
let ascii = (offset..offset+bytes_row)
.map(|offset| {
format!("{}", ASCII.chars().nth(accessor(offset) as _).unwrap_or('.'))
}).collect::<String>();
ui.monospace(format!("{:06X} {} {}", i * 16, bytes, ascii));
}
});
});
self.memory.render(&ctx, &emu);
}
}

View file

@ -1,4 +1,4 @@
pub fn palette_window(ctx: &egui::Context, emu: &emu::Megadrive) {
pub fn palette_window(ctx: &egui::Context, emu: &mut emu::Megadrive) {
egui::Window::new("palette")
.show(ctx, |ui| {
let pixels = emu.core.mem.vdp.cram_rgb.iter()
@ -15,5 +15,16 @@ pub fn palette_window(ctx: &egui::Context, emu: &emu::Megadrive) {
let img = egui::Image::new(texture, texture.size_vec2() * 20.);
ui.add(img);
// let rgb = &emu.core.mem.vdp.cram_rgb;
// for i in 0..rgb.len() {
// let (r, g, b) = emu.core.mem.vdp.cram_rgb[i];
// let mut color = [r, g, b];
// ui.color_edit_button_srgb(&mut color);
// emu.core.mem.vdp.cram_rgb[i] = (color[0], color[1], color[2]);
// }
});
}

View file

@ -4,7 +4,6 @@ mod input;
mod widgets;
pub use app::App;
#[cfg(target_arch = "wasm32")]
use eframe::wasm_bindgen::{self, prelude::*};

View file

@ -0,0 +1,171 @@
// use std::future::Future;
// use eframe::{egui, epi};
// use rfd;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{window, console, Element, HtmlInputElement, FileReader};
use js_sys::{Uint8Array, ArrayBuffer, Object};
pub enum Message {
File(Vec<u8>),
// Other messages
}
pub struct FileDialog {
tx: std::sync::mpsc::Sender<Message>,
rx: std::sync::mpsc::Receiver<Message>,
input: HtmlInputElement,
closure: Option<Closure<dyn FnMut()>>,
}
// #[cfg(not(target_arch = "wasm32"))]
impl Default for FileDialog {
#[cfg(target_arch = "wasm32")]
fn default() -> Self {
let (tx, rx) = std::sync::mpsc::channel();
let document = window().unwrap().document().unwrap();
let body = document.body().unwrap();
let input = document.create_element("input").unwrap().dyn_into::<HtmlInputElement>().unwrap();
input.set_attribute("type", "file").unwrap();
input.style().set_property("display", "none").unwrap();
body.append_child(&input).unwrap();
// let input_clone = input.clone();
// let closure = Closure::wrap(Box::new(move || {
// if let Some(file) = input_clone.files().and_then(|files| files.get(0)) {
// let reader = FileReader::new().unwrap();
// let reader_clone = reader.clone();
// let onload_closure = Closure::wrap(Box::new(move || {
// let array_buffer = reader_clone.result().unwrap().dyn_into::<ArrayBuffer>().unwrap();
// let buffer = Uint8Array::new(&array_buffer).to_vec();
// console::log_1(&format!("File data buffer: {:?}", buffer).into());
// }) as Box<dyn FnMut()>);
// reader.set_onload(Some(onload_closure.as_ref().unchecked_ref()));
// reader.read_as_array_buffer(&file).unwrap();
// onload_closure.forget();
// }
// }) as Box<dyn FnMut()>);
// input.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref()).unwrap();
// closure.forget();
Self {
rx,
tx,
input,
closure: None,
}
}
}
impl Drop for FileDialog {
fn drop(&mut self) {
self.input.remove();
}
}
impl FileDialog {
pub fn open_file(&mut self) {
if let Some(closure) = &self.closure {
self.input.remove_event_listener_with_callback("change", closure.as_ref().unchecked_ref()).unwrap();
std::mem::replace(&mut self.closure, None).unwrap().forget();
}
let tx = self.tx.clone();
let input_clone = self.input.clone();
let closure = Closure::once(move || {
if let Some(file) = input_clone.files().and_then(|files| files.get(0)) {
let reader = FileReader::new().unwrap();
let reader_clone = reader.clone();
let onload_closure = Closure::once(Box::new(move || {
let array_buffer = reader_clone.result().unwrap().dyn_into::<ArrayBuffer>().unwrap();
let buffer = Uint8Array::new(&array_buffer).to_vec();
console::log_1(&format!("File data buffer: {:?}", buffer).into());
tx.send(Message::File(buffer));
}));
reader.set_onload(Some(onload_closure.as_ref().unchecked_ref()));
reader.read_as_array_buffer(&file).unwrap();
onload_closure.forget();
}
});
self.input.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref()).unwrap();
self.closure = Some(closure);
self.input.click();
// if ui.button("Open file…").clicked() {
// if let Some(path) = rfd::AsyncFileDialog::new().pick_file() {
// let _ = Some(path.display().to_string());
// }
// }
}
}
// pub fn filedialog(state: FileState) ->
// impl egui::Widget
// {
// move |ui: &mut egui::Ui| {
// }
// impl epi::App for FileApp {
// fn name(&self) -> &str {
// "file dialog app"
// }
// fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
// // This is important, otherwise file dialog can hang
// // and messages are not processed
// ctx.request_repaint();
// loop {
// match self.message_channel.1.try_recv() {
// Ok(_message) => {
// // Process FileOpen and other messages
// }
// Err(_) => {
// break;
// }
// }
// }
// egui::CentralPanel::default().show(ctx, |ui| {
// let open_button = ui.add(egui::widgets::Button::new("Open..."));
// if open_button.clicked() {
// let task = rfd::AsyncFileDialog::new()
// .add_filter("Text files", &["txt"])
// .set_directory("/")
// .pick_file();
// let message_sender = self.message_channel.0.clone();
// execute(async move {
// let file = task.await;
// if let Some(file) = file {
// message_sender.send(file.read().await).ok();
// }
// });
// }
// });
// }
// }
// #[cfg(not(target_arch = "wasm32"))]
// fn execute<F: Future<Output = ()> + Send + 'static>(f: F) {
// std::thread::spawn(move || futures::executor::block_on(f));
// }
// #[cfg(target_arch = "wasm32")]
// fn execute<F: Future<Output = ()> + 'static>(f: F) {
// wasm_bindgen_futures::spawn_local(f);
// }

View file

@ -2,23 +2,5 @@ pub mod viewport;
pub use viewport::*;
pub mod menu;
pub use menu::*;
// misc;
// #[cfg(target_arch = "wasm32")]
// egui::Window::new("file input").show(ctx, |ui| {
// use eframe::{wasm_bindgen, web_sys};
// use wasm_bindgen::JsCast;
// if ui.button("file").clicked() {
// let task = rfd::AsyncFileDialog::new().pick_file();
// wasm_bindgen_futures::spawn_local(async {
// if let Some(file) = task.await {
// web_sys::console::log_1(&format!("{:?}", file.read().await).into());
// }
// });
// }
// });
pub mod file;
pub use file::*;