use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; use egui::{ menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder, ViewportCommand, ViewportId, WidgetText, }; use super::{game_screen::GameScreen, AppWindow}; pub struct GameWindow { client: EmulatorClient, sim_id: SimId, input_window_open: Arc, screen: Option, } impl GameWindow { pub fn new(client: EmulatorClient, sim_id: SimId, input_window_open: Arc) -> Self { Self { client, sim_id, input_window_open, screen: None, } } pub fn init_renderer(&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) } fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) { ui.menu_button("ROM", |ui| { if ui.button("Open ROM").clicked() { let rom = rfd::FileDialog::new() .add_filter("Virtual Boy ROMs", &["vb", "vbrom"]) .pick_file(); if let Some(path) = rom { self.client .send_command(EmulatorCommand::LoadGame(SimId::Player1, path)); } ui.close_menu(); } if ui.button("Quit").clicked() { ctx.send_viewport_cmd(ViewportCommand::Close); } }); ui.menu_button("Emulation", |ui| { let has_game = self.client.has_game(self.sim_id); if self.client.is_running(self.sim_id) { if ui.add_enabled(has_game, Button::new("Pause")).clicked() { self.client.send_command(EmulatorCommand::Pause); ui.close_menu(); } } else if ui.add_enabled(has_game, Button::new("Resume")).clicked() { self.client.send_command(EmulatorCommand::Resume); ui.close_menu(); } if ui.add_enabled(has_game, Button::new("Reset")).clicked() { self.client .send_command(EmulatorCommand::Reset(self.sim_id)); ui.close_menu(); } }); ui.menu_button("Video", |ui| { let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap()); let current_dims = current_dims.max - current_dims.min; for scale in 1..=4 { let label = format!("x{scale}"); let scale = scale as f32; let dims = (384.0 * scale, 224.0 * scale + 20.0).into(); if ui .selectable_button((current_dims - dims).length() < 1.0, label) .clicked() { ctx.send_viewport_cmd(ViewportCommand::InnerSize(dims)); ui.close_menu(); } } }); ui.menu_button("Audio", |ui| { let p1_enabled = self.client.is_audio_enabled(SimId::Player1); let p2_enabled = self.client.is_audio_enabled(SimId::Player2); if ui.selectable_button(p1_enabled, "Player 1").clicked() { self.client .send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled)); ui.close_menu(); } if ui.selectable_button(p2_enabled, "Player 2").clicked() { self.client .send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled)); ui.close_menu(); } }); ui.menu_button("Input", |ui| { if ui.button("Bind Inputs").clicked() { self.input_window_open.store(true, Ordering::Relaxed); ui.close_menu(); } }); ui.menu_button("Multiplayer", |ui| { if self.sim_id == SimId::Player1 && !self.client.has_player_2() && ui.button("Open Player 2").clicked() { self.client .send_command(EmulatorCommand::StartSecondSim(None)); ui.close_menu(); } if self.client.has_player_2() { let linked = self.client.are_sims_linked(); if linked && ui.button("Unlink").clicked() { self.client.send_command(EmulatorCommand::Unlink); ui.close_menu(); } if !linked && ui.button("Link").clicked() { self.client.send_command(EmulatorCommand::Link); ui.close_menu(); } } }); } } impl AppWindow for GameWindow { fn viewport_id(&self) -> ViewportId { match self.sim_id { SimId::Player1 => ViewportId::ROOT, SimId::Player2 => ViewportId::from_hash_of("Player2"), } } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title("Shrooms VB") .with_inner_size((384.0, 244.0)) } fn show(&mut self, ctx: &Context) { TopBottomPanel::top("menubar") .exact_height(20.0) .show(ctx, |ui| { menu::bar(ui, |ui| { self.show_menu(ctx, ui); }); }); CentralPanel::default().show(ctx, |ui| { if let Some(screen) = self.screen.as_ref() { ui.add(screen); } }); } } trait UiExt { fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response; } impl UiExt for Ui { fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response { self.style_mut().visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT; self.style_mut().visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT; self.style_mut().visuals.widgets.active.bg_fill = Color32::TRANSPARENT; let mut selected = selected; self.checkbox(&mut selected, text) } }