Compare commits

...

8 Commits
v0.9.0 ... main

13 changed files with 74 additions and 40 deletions

2
Cargo.lock generated
View File

@ -2165,7 +2165,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "lemur" name = "lemur"
version = "0.9.0" version = "0.9.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atoi", "atoi",

View File

@ -4,7 +4,7 @@ description = "An emulator for the Virtual Boy."
repository = "https://git.virtual-boy.com/PVB/lemur" repository = "https://git.virtual-boy.com/PVB/lemur"
publish = false publish = false
license = "MIT" license = "MIT"
version = "0.9.0" version = "0.9.2"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

@ -1 +1 @@
Subproject commit 8598eab087cced12b92a411f6c7f2eb9de51310f Subproject commit 4ed3b7299507b8ea0079a0965f33b0c8a6886572

View File

@ -24,8 +24,8 @@ use crate::{
persistence::Persistence, persistence::Persistence,
window::{ window::{
AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow, AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow,
GdbServerWindow, HotkeysWindow, InputWindow, ObjectWindow, ProfileWindow, RegisterWindow, GdbServerWindow, HotkeysWindow, InitArgs, InputWindow, ObjectWindow, ProfileWindow,
TerminalWindow, WorldWindow, RegisterWindow, TerminalWindow, WorldWindow,
}, },
}; };
@ -423,7 +423,13 @@ impl Viewport {
let (window, state) = create_window_and_state(&ctx, event_loop, &builder, &mut painter); let (window, state) = create_window_and_state(&ctx, event_loop, &builder, &mut painter);
egui_winit::update_viewport_info(&mut info, &ctx, &window, true); egui_winit::update_viewport_info(&mut info, &ctx, &window, true);
app.on_init(&ctx, painter.render_state().as_ref().unwrap()); let render_state = painter.render_state();
let args = InitArgs {
ctx: &ctx,
window: &window,
render_state: render_state.as_ref().unwrap(),
};
app.on_init(args);
Self { Self {
painter, painter,
ctx, ctx,

View File

@ -154,14 +154,14 @@ fn extract_isx_symbols(input: &[u8]) -> Option<Vec<Symbol>> {
// Range (Virtual Boy) // Range (Virtual Boy)
let count_bytes; let count_bytes;
(count_bytes, buf) = buf.split_first_chunk()?; (count_bytes, buf) = buf.split_first_chunk()?;
let count = u16::from_le_bytes(*count_bytes) + 1; let count = u16::from_le_bytes(*count_bytes);
(_, buf) = buf.split_at_checked(count as usize * 9)?; (_, buf) = buf.split_at_checked(count as usize * 9)?;
} }
0x14 => { 0x14 => {
// Symbol (Virtual Boy) // Symbol (Virtual Boy)
let count_bytes; let count_bytes;
(count_bytes, buf) = buf.split_first_chunk()?; (count_bytes, buf) = buf.split_first_chunk()?;
let count = u16::from_le_bytes(*count_bytes) + 1; let count = u16::from_le_bytes(*count_bytes);
for _ in 0..count { for _ in 0..count {
let name_len; let name_len;
(name_len, buf) = buf.split_first()?; (name_len, buf) = buf.split_first()?;

View File

@ -1,3 +1,5 @@
use std::sync::Arc;
pub use about::AboutWindow; pub use about::AboutWindow;
use egui::{Context, ViewportBuilder, ViewportId}; use egui::{Context, ViewportBuilder, ViewportId};
pub use game::GameWindow; pub use game::GameWindow;
@ -9,7 +11,7 @@ pub use terminal::TerminalWindow;
pub use vip::{ pub use vip::{
BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow,
}; };
use winit::event::KeyEvent; use winit::{event::KeyEvent, window::Window};
use crate::emulator::SimId; use crate::emulator::SimId;
@ -31,9 +33,8 @@ pub trait AppWindow {
} }
fn initial_viewport(&self) -> ViewportBuilder; fn initial_viewport(&self) -> ViewportBuilder;
fn show(&mut self, ctx: &Context); fn show(&mut self, ctx: &Context);
fn on_init(&mut self, ctx: &Context, render_state: &egui_wgpu::RenderState) { fn on_init(&mut self, args: InitArgs) {
let _ = ctx; let _ = args;
let _ = render_state;
} }
fn on_destroy(&mut self) {} fn on_destroy(&mut self) {}
fn handle_key_event(&mut self, event: &KeyEvent) -> bool { fn handle_key_event(&mut self, event: &KeyEvent) -> bool {
@ -45,3 +46,9 @@ pub trait AppWindow {
false false
} }
} }
pub struct InitArgs<'a> {
pub ctx: &'a Context,
pub window: &'a Arc<Window>,
pub render_state: &'a egui_wgpu::RenderState,
}

View File

@ -1,10 +1,14 @@
use std::{sync::mpsc, time::Duration}; use std::{
sync::{Arc, mpsc},
time::Duration,
};
use crate::{ use crate::{
app::UserEvent, app::UserEvent,
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState}, emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
input::{Command, ShortcutProvider}, input::{Command, ShortcutProvider},
persistence::Persistence, persistence::Persistence,
window::InitArgs,
}; };
use anyhow::Context as _; use anyhow::Context as _;
use egui::{ use egui::{
@ -47,6 +51,7 @@ pub struct GameWindow {
screen: Option<GameScreen>, screen: Option<GameScreen>,
messages: Option<mpsc::Receiver<Toast>>, messages: Option<mpsc::Receiver<Toast>>,
color_picker: Option<ColorPickerState>, color_picker: Option<ColorPickerState>,
window: Option<Arc<winit::window::Window>>,
} }
impl GameWindow { impl GameWindow {
@ -73,6 +78,7 @@ impl GameWindow {
screen: None, screen: None,
messages: None, messages: None,
color_picker: None, color_picker: None,
window: None,
} }
} }
@ -87,7 +93,7 @@ impl GameWindow {
match command { match command {
Command::OpenRom => { Command::OpenRom => {
let rom = rfd::FileDialog::new() let rom = rfd::FileDialog::new()
.add_filter("Virtual Boy ROMs", &["vb", "vbrom"]) .add_filter("Virtual Boy ROMs", &["vb", "vbrom", "elf", "isx"])
.pick_file(); .pick_file();
if let Some(path) = rom { if let Some(path) = rom {
self.client self.client
@ -139,7 +145,7 @@ impl GameWindow {
.clicked() .clicked()
{ {
let rom = rfd::FileDialog::new() let rom = rfd::FileDialog::new()
.add_filter("Virtual Boy ROMs", &["vb", "vbrom"]) .add_filter("Virtual Boy ROMs", &["vb", "vbrom", "elf", "isx"])
.pick_file(); .pick_file();
if let Some(path) = rom { if let Some(path) = rom {
self.client self.client
@ -298,10 +304,13 @@ impl GameWindow {
self.client self.client
.send_command(EmulatorCommand::Screenshot(self.sim_id, tx)); .send_command(EmulatorCommand::Screenshot(self.sim_id, tx));
let bytes = rx.await.context("Could not take screenshot")?; let bytes = rx.await.context("Could not take screenshot")?;
let file = rfd::FileDialog::new() let mut file_dialog = rfd::FileDialog::new()
.add_filter("PNG images", &["png"]) .add_filter("PNG images", &["png"])
.set_file_name("screenshot.png") .set_file_name("screenshot.png");
.save_file(); if let Some(window) = self.window.as_ref() {
file_dialog = file_dialog.set_parent(window);
}
let file = file_dialog.save_file();
let Some(path) = file else { let Some(path) = file else {
return Ok(None); return Ok(None);
}; };
@ -545,8 +554,8 @@ impl AppWindow for GameWindow {
self.toasts.show(ctx); self.toasts.show(ctx);
} }
fn on_init(&mut self, _ctx: &Context, render_state: &egui_wgpu::RenderState) { fn on_init(&mut self, args: InitArgs) {
let (screen, sink) = GameScreen::init(render_state); let (screen, sink) = GameScreen::init(args.render_state);
let (message_sink, message_source) = mpsc::channel(); let (message_sink, message_source) = mpsc::channel();
self.client.send_command(EmulatorCommand::ConnectToSim( self.client.send_command(EmulatorCommand::ConnectToSim(
self.sim_id, self.sim_id,
@ -555,6 +564,7 @@ impl AppWindow for GameWindow {
)); ));
self.screen = Some(screen); self.screen = Some(screen);
self.messages = Some(message_source); self.messages = Some(message_source);
self.window = Some(args.window.clone());
} }
fn on_destroy(&mut self) { fn on_destroy(&mut self) {

View File

@ -1,13 +1,14 @@
use std::{fs, time::Duration}; use std::{fs, sync::Arc, time::Duration};
use anyhow::Result; use anyhow::Result;
use egui::{Button, CentralPanel, Checkbox, Label, ViewportBuilder, ViewportId}; use egui::{Button, CentralPanel, Checkbox, Label, ViewportBuilder, ViewportId};
use egui_notify::{Anchor, Toast, Toasts}; use egui_notify::{Anchor, Toast, Toasts};
use winit::window::Window;
use crate::{ use crate::{
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId}, emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId},
profiler::{Profiler, ProfilerStatus}, profiler::{Profiler, ProfilerStatus},
window::AppWindow, window::{AppWindow, InitArgs},
}; };
pub struct ProfileWindow { pub struct ProfileWindow {
@ -15,6 +16,7 @@ pub struct ProfileWindow {
client: EmulatorClient, client: EmulatorClient,
profiler: Profiler, profiler: Profiler,
toasts: Toasts, toasts: Toasts,
window: Option<Arc<Window>>,
} }
impl ProfileWindow { impl ProfileWindow {
@ -27,6 +29,7 @@ impl ProfileWindow {
.with_anchor(Anchor::BottomLeft) .with_anchor(Anchor::BottomLeft)
.with_margin((10.0, 10.0).into()) .with_margin((10.0, 10.0).into())
.reverse(true), .reverse(true),
window: None,
} }
} }
@ -59,12 +62,16 @@ impl ProfileWindow {
fn try_finish_recording(&mut self) -> Result<Option<String>> { fn try_finish_recording(&mut self) -> Result<Option<String>> {
let bytes_receiver = self.profiler.finish_recording(); let bytes_receiver = self.profiler.finish_recording();
let file = rfd::FileDialog::new() let mut file_dialog = rfd::FileDialog::new()
.add_filter("Profiler files", &["json"]) .add_filter("Profiler files", &["json"])
.set_file_name("profile.json") .set_file_name("profile.json");
.save_file(); if let Some(window) = self.window.as_ref() {
file_dialog = file_dialog.set_parent(window);
}
let file = file_dialog.save_file();
if let Some(path) = file { if let Some(path) = file {
let bytes = pollster::block_on(bytes_receiver)?; let bytes = pollster::block_on(bytes_receiver)?;
let _ = fs::remove_file(&path);
fs::write(&path, bytes)?; fs::write(&path, bytes)?;
Ok(Some(path.display().to_string())) Ok(Some(path.display().to_string()))
} else { } else {
@ -159,4 +166,8 @@ impl AppWindow for ProfileWindow {
}); });
self.toasts.show(ctx); self.toasts.show(ctx);
} }
fn on_init(&mut self, args: InitArgs) {
self.window = Some(args.window.clone());
}
} }

View File

@ -11,7 +11,7 @@ use crate::{
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView}, memory::{MemoryClient, MemoryView},
window::{ window::{
AppWindow, AppWindow, InitArgs,
utils::{NumberEdit, UiExt}, utils::{NumberEdit, UiExt},
}, },
}; };
@ -187,8 +187,8 @@ impl AppWindow for BgMapWindow {
.with_inner_size((640.0, 480.0)) .with_inner_size((640.0, 480.0))
} }
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { fn on_init(&mut self, args: InitArgs) {
ctx.add_texture_loader(self.loader.clone()); args.ctx.add_texture_loader(self.loader.clone());
} }
fn show(&mut self, ctx: &Context) { fn show(&mut self, ctx: &Context) {

View File

@ -12,7 +12,7 @@ use crate::{
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView}, memory::{MemoryClient, MemoryView},
window::{ window::{
AppWindow, AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _}, utils::{NumberEdit, UiExt as _},
}, },
}; };
@ -251,8 +251,8 @@ impl AppWindow for CharacterDataWindow {
.with_inner_size((640.0, 480.0)) .with_inner_size((640.0, 480.0))
} }
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { fn on_init(&mut self, args: InitArgs) {
ctx.add_texture_loader(self.loader.clone()); args.ctx.add_texture_loader(self.loader.clone());
} }
fn show(&mut self, ctx: &Context) { fn show(&mut self, ctx: &Context) {

View File

@ -11,7 +11,7 @@ use crate::{
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView}, memory::{MemoryClient, MemoryView},
window::{ window::{
AppWindow, AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _}, utils::{NumberEdit, UiExt as _},
}, },
}; };
@ -153,8 +153,8 @@ impl AppWindow for FrameBufferWindow {
.with_inner_size((640.0, 480.0)) .with_inner_size((640.0, 480.0))
} }
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { fn on_init(&mut self, args: InitArgs) {
ctx.add_texture_loader(self.loader.clone()); args.ctx.add_texture_loader(self.loader.clone());
} }
fn show(&mut self, ctx: &Context) { fn show(&mut self, ctx: &Context) {

View File

@ -11,7 +11,7 @@ use crate::{
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView}, memory::{MemoryClient, MemoryView},
window::{ window::{
AppWindow, AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _}, utils::{NumberEdit, UiExt as _},
}, },
}; };
@ -208,8 +208,8 @@ impl AppWindow for ObjectWindow {
.with_inner_size((640.0, 500.0)) .with_inner_size((640.0, 500.0))
} }
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { fn on_init(&mut self, args: InitArgs) {
ctx.add_texture_loader(self.loader.clone()); args.ctx.add_texture_loader(self.loader.clone());
} }
fn show(&mut self, ctx: &Context) { fn show(&mut self, ctx: &Context) {

View File

@ -17,7 +17,7 @@ use crate::{
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryRef, MemoryView}, memory::{MemoryClient, MemoryRef, MemoryView},
window::{ window::{
AppWindow, AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _}, utils::{NumberEdit, UiExt as _},
}, },
}; };
@ -523,8 +523,8 @@ impl AppWindow for WorldWindow {
.with_inner_size((640.0, 520.0)) .with_inner_size((640.0, 520.0))
} }
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { fn on_init(&mut self, args: InitArgs) {
ctx.add_texture_loader(self.loader.clone()); args.ctx.add_texture_loader(self.loader.clone());
} }
fn show(&mut self, ctx: &Context) { fn show(&mut self, ctx: &Context) {