diff --git a/Cargo.lock b/Cargo.lock index 9a2db48..6d35ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -908,6 +908,15 @@ dependencies = [ "nohash-hasher", ] +[[package]] +name = "egui-toast" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95d58a44bf48161fe6a3f12386722e99bf86b2c16d23c2d04f6a48a95a5923d2" +dependencies = [ + "egui", +] + [[package]] name = "egui-wgpu" version = "0.29.1" @@ -1711,6 +1720,7 @@ dependencies = [ "clap", "cpal", "egui", + "egui-toast", "egui-wgpu", "egui-winit", "egui_extras", @@ -1732,9 +1742,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" @@ -2690,15 +2700,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7189cb2..26c6cd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ clap = { version = "4", features = ["derive"] } cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" } egui = "0.29" egui_extras = "0.29" +egui-toast = "0.15" egui-winit = "0.29" egui-wgpu = { version = "0.29", features = ["winit"] } gilrs = "0.11" diff --git a/src/emulator.rs b/src/emulator.rs index d0ca5e9..672e5c9 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -11,6 +11,7 @@ use std::{ }; use anyhow::Result; +use egui_toast::{Toast, ToastKind, ToastOptions}; use crate::{audio::Audio, graphics::TextureSink}; pub use shrooms_vb_core::VBKey; @@ -139,6 +140,7 @@ pub struct Emulator { audio_on: Arc<[AtomicBool; 2]>, linked: Arc, renderers: HashMap, + messages: HashMap>, } impl Emulator { @@ -161,6 +163,7 @@ impl Emulator { audio_on, linked, renderers: HashMap::new(), + messages: HashMap::new(), }) } @@ -330,28 +333,35 @@ impl Emulator { fn handle_command(&mut self, command: EmulatorCommand) { match command { - EmulatorCommand::SetRenderer(sim_id, renderer) => { + EmulatorCommand::ConnectToSim(sim_id, renderer, messages) => { self.renderers.insert(sim_id, renderer); + self.messages.insert(sim_id, messages); } EmulatorCommand::LoadGame(sim_id, path) => { if let Err(error) = self.load_cart(sim_id, &path) { - eprintln!("error loading rom: {}", error); + self.report_error(sim_id, format!("Error loading rom: {error}")); } } EmulatorCommand::StartSecondSim(path) => { if let Err(error) = self.start_second_sim(path) { - eprintln!("error starting second sim: {}", error); + self.report_error( + SimId::Player2, + format!("Error starting second sim: {error}"), + ); } } EmulatorCommand::StopSecondSim => { if let Err(error) = self.stop_second_sim() { - eprintln!("error stopping second sim: {}", error); + self.report_error( + SimId::Player2, + format!("Error stopping second sim: {error}"), + ); } } EmulatorCommand::Pause => { for sim_id in SimId::values() { if let Err(error) = self.pause_sim(sim_id) { - eprintln!("error pausing: {}", error); + self.report_error(sim_id, format!("Error pausing: {error}")); } } } @@ -375,7 +385,7 @@ impl Emulator { } EmulatorCommand::Reset(sim_id) => { if let Err(error) = self.reset_sim(sim_id, None) { - eprintln!("error resetting sim: {}", error); + self.report_error(sim_id, format!("Error resetting sim: {error}")); } } EmulatorCommand::SetKeys(sim_id, keys) => { @@ -386,18 +396,35 @@ impl Emulator { EmulatorCommand::Exit(done) => { for sim_id in SimId::values() { if let Err(error) = self.save_sram(sim_id) { - eprintln!("error saving sram on exit: {}", error); + self.report_error(sim_id, format!("Error saving sram on exit: {error}")); } } let _ = done.send(()); } } } + + fn report_error(&self, sim_id: SimId, message: String) { + let messages = self + .messages + .get(&sim_id) + .or_else(|| self.messages.get(&SimId::Player1)); + if let Some(msg) = messages { + let toast = Toast::new() + .kind(ToastKind::Error) + .options(ToastOptions::default().duration_in_seconds(5.0)) + .text(&message); + if msg.send(toast).is_ok() { + return; + } + } + eprintln!("{}", message); + } } #[derive(Debug)] pub enum EmulatorCommand { - SetRenderer(SimId, TextureSink), + ConnectToSim(SimId, TextureSink, mpsc::Sender), LoadGame(SimId, PathBuf), StartSecondSim(Option), StopSecondSim, diff --git a/src/window/game.rs b/src/window/game.rs index b154e30..4b83ca0 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -1,12 +1,15 @@ +use std::sync::mpsc; + use crate::{ app::UserEvent, emulator::{EmulatorClient, EmulatorCommand, SimId}, }; use egui::{ - ecolor::HexColor, menu, Align2, Button, CentralPanel, Color32, Context, Frame, Layout, - Response, Sense, TopBottomPanel, Ui, Vec2, ViewportBuilder, ViewportCommand, ViewportId, - WidgetText, Window, + ecolor::HexColor, menu, Align2, Button, CentralPanel, Color32, Context, Direction, Frame, + Layout, Response, Sense, TopBottomPanel, Ui, Vec2, ViewportBuilder, ViewportCommand, + ViewportId, WidgetText, Window, }; +use egui_toast::{Toast, Toasts}; use winit::event_loop::EventLoopProxy; use super::{ @@ -36,6 +39,7 @@ pub struct GameWindow { display_mode: DisplayMode, colors: [Color32; 2], screen: Option, + messages: Option>, color_picker: Option, } @@ -48,6 +52,7 @@ impl GameWindow { display_mode: DisplayMode::Anaglyph, colors: COLOR_PRESETS[0], screen: None, + messages: None, color_picker: None, } } @@ -257,6 +262,14 @@ impl AppWindow for GameWindow { } fn show(&mut self, ctx: &Context) { + let mut toasts = Toasts::new() + .anchor(Align2::LEFT_BOTTOM, (10.0, 10.0)) + .direction(Direction::BottomUp); + if let Some(messages) = self.messages.as_mut() { + while let Ok(toast) = messages.try_recv() { + toasts.add(toast); + } + } TopBottomPanel::top("menubar") .exact_height(22.0) .show(ctx, |ui| { @@ -280,13 +293,19 @@ impl AppWindow for GameWindow { ui.add(screen); } }); + toasts.show(ctx); } fn on_init(&mut self, render_state: &egui_wgpu::RenderState) { let (screen, sink) = GameScreen::init(render_state); - self.client - .send_command(EmulatorCommand::SetRenderer(self.sim_id, sink)); - self.screen = Some(screen) + let (message_sink, message_source) = mpsc::channel(); + self.client.send_command(EmulatorCommand::ConnectToSim( + self.sim_id, + sink, + message_sink, + )); + self.screen = Some(screen); + self.messages = Some(message_source); } fn on_destroy(&mut self) {