mirror of
https://github.com/kirjavascript/trueLMAO.git
synced 2024-05-20 13:17:26 -04:00
wasm filedialog
This commit is contained in:
parent
14093014af
commit
89c460c184
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
|
69
frontend/src/debug/memory.rs
Normal file
69
frontend/src/debug/memory.rs
Normal 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));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
// }
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ mod input;
|
|||
mod widgets;
|
||||
pub use app::App;
|
||||
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::wasm_bindgen::{self, prelude::*};
|
||||
|
||||
|
|
171
frontend/src/widgets/file.rs
Normal file
171
frontend/src/widgets/file.rs
Normal 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);
|
||||
// }
|
|
@ -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::*;
|
||||
|
|
Loading…
Reference in a new issue