2024-11-28 15:27:18 +00:00
|
|
|
use crate::{
|
|
|
|
app::UserEvent,
|
|
|
|
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
2024-11-26 05:38:03 +00:00
|
|
|
};
|
|
|
|
use egui::{
|
2024-12-01 05:45:13 +00:00
|
|
|
menu, Button, CentralPanel, Color32, Context, Frame, Response, Sense, TopBottomPanel, Ui, Vec2,
|
2024-11-28 17:39:08 +00:00
|
|
|
ViewportBuilder, ViewportCommand, ViewportId, WidgetText,
|
2024-11-26 05:38:03 +00:00
|
|
|
};
|
2024-11-28 15:27:18 +00:00
|
|
|
use winit::event_loop::EventLoopProxy;
|
2024-11-26 05:38:03 +00:00
|
|
|
|
2024-12-01 04:14:01 +00:00
|
|
|
use super::{
|
|
|
|
game_screen::{DisplayMode, GameScreen},
|
|
|
|
AppWindow,
|
|
|
|
};
|
2024-11-26 05:38:03 +00:00
|
|
|
|
2024-12-01 05:45:13 +00:00
|
|
|
const COLOR_PRESETS: [[Color32; 2]; 3] = [
|
|
|
|
[
|
|
|
|
Color32::from_rgb(0xff, 0x00, 0x00),
|
|
|
|
Color32::from_rgb(0x00, 0xc6, 0xf0),
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Color32::from_rgb(0x00, 0xb4, 0x00),
|
|
|
|
Color32::from_rgb(0xc8, 0x00, 0xff),
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Color32::from_rgb(0xb4, 0x9b, 0x00),
|
|
|
|
Color32::from_rgb(0x00, 0x00, 0xff),
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
2024-11-26 05:38:03 +00:00
|
|
|
pub struct GameWindow {
|
|
|
|
client: EmulatorClient,
|
2024-11-28 15:27:18 +00:00
|
|
|
proxy: EventLoopProxy<UserEvent>,
|
2024-11-26 05:38:03 +00:00
|
|
|
sim_id: SimId,
|
2024-12-01 04:14:01 +00:00
|
|
|
display_mode: DisplayMode,
|
2024-12-01 05:45:13 +00:00
|
|
|
colors: [Color32; 2],
|
2024-11-26 05:38:03 +00:00
|
|
|
screen: Option<GameScreen>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GameWindow {
|
2024-11-28 15:27:18 +00:00
|
|
|
pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>, sim_id: SimId) -> Self {
|
2024-11-26 05:38:03 +00:00
|
|
|
Self {
|
|
|
|
client,
|
2024-11-28 15:27:18 +00:00
|
|
|
proxy,
|
2024-11-26 05:38:03 +00:00
|
|
|
sim_id,
|
2024-12-01 04:14:01 +00:00
|
|
|
display_mode: DisplayMode::Anaglyph,
|
2024-12-01 05:45:13 +00:00
|
|
|
colors: COLOR_PRESETS[0],
|
2024-11-26 05:38:03 +00:00
|
|
|
screen: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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| {
|
2024-12-01 04:14:01 +00:00
|
|
|
ui.menu_button("Screen Size", |ui| {
|
|
|
|
let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap());
|
|
|
|
let current_dims = current_dims.max - current_dims.min;
|
2024-11-26 05:38:03 +00:00
|
|
|
|
2024-12-01 04:14:01 +00:00
|
|
|
for scale in 1..=4 {
|
|
|
|
let label = format!("x{scale}");
|
|
|
|
let scale = scale as f32;
|
2024-12-01 20:52:11 +00:00
|
|
|
let dims = {
|
|
|
|
let Vec2 { x, y } = self.display_mode.proportions();
|
|
|
|
Vec2::new(x * scale, y * scale + 22.0)
|
|
|
|
};
|
2024-12-01 04:14:01 +00:00
|
|
|
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("Display Mode", |ui| {
|
2024-12-01 20:52:11 +00:00
|
|
|
let old_proportions = self.display_mode.proportions();
|
2024-11-26 05:38:03 +00:00
|
|
|
if ui
|
2024-12-01 04:14:01 +00:00
|
|
|
.selectable_option(&mut self.display_mode, DisplayMode::Anaglyph, "Anaglyph")
|
2024-11-26 05:38:03 +00:00
|
|
|
.clicked()
|
|
|
|
{
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
2024-12-01 04:14:01 +00:00
|
|
|
if ui
|
|
|
|
.selectable_option(&mut self.display_mode, DisplayMode::LeftEye, "Left Eye")
|
|
|
|
.clicked()
|
|
|
|
{
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
|
|
|
if ui
|
|
|
|
.selectable_option(&mut self.display_mode, DisplayMode::RightEye, "Right Eye")
|
|
|
|
.clicked()
|
|
|
|
{
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
2024-12-01 20:52:11 +00:00
|
|
|
if ui
|
|
|
|
.selectable_option(
|
|
|
|
&mut self.display_mode,
|
|
|
|
DisplayMode::SideBySide,
|
|
|
|
"Side by Side",
|
|
|
|
)
|
|
|
|
.clicked()
|
|
|
|
{
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
|
|
|
|
|
|
|
let new_proportions = self.display_mode.proportions();
|
|
|
|
let scale = new_proportions / old_proportions;
|
|
|
|
if scale != Vec2::new(1.0, 1.0) {
|
|
|
|
let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap());
|
|
|
|
let current_dims = current_dims.max - current_dims.min;
|
|
|
|
|
|
|
|
ctx.send_viewport_cmd(ViewportCommand::InnerSize(current_dims * scale));
|
|
|
|
}
|
2024-12-01 04:14:01 +00:00
|
|
|
});
|
2024-12-01 05:45:13 +00:00
|
|
|
ui.menu_button("Colors", |ui| {
|
|
|
|
for preset in COLOR_PRESETS {
|
|
|
|
if ui.color_pair_button(preset[0], preset[1]).clicked() {
|
|
|
|
self.colors = preset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2024-11-26 05:38:03 +00:00
|
|
|
});
|
|
|
|
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() {
|
2024-11-28 15:27:18 +00:00
|
|
|
self.proxy.send_event(UserEvent::OpenInput).unwrap();
|
2024-11-26 05:38:03 +00:00
|
|
|
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));
|
2024-11-28 15:27:18 +00:00
|
|
|
self.proxy.send_event(UserEvent::OpenPlayer2).unwrap();
|
2024-11-26 05:38:03 +00:00
|
|
|
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 {
|
2024-12-01 20:52:11 +00:00
|
|
|
let dimensions = self.display_mode.proportions() + Vec2::new(0.0, 22.0);
|
2024-11-26 05:38:03 +00:00
|
|
|
ViewportBuilder::default()
|
|
|
|
.with_title("Shrooms VB")
|
2024-12-01 20:52:11 +00:00
|
|
|
.with_inner_size(dimensions)
|
2024-11-26 05:38:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn show(&mut self, ctx: &Context) {
|
|
|
|
TopBottomPanel::top("menubar")
|
2024-11-28 17:39:08 +00:00
|
|
|
.exact_height(22.0)
|
2024-11-26 05:38:03 +00:00
|
|
|
.show(ctx, |ui| {
|
|
|
|
menu::bar(ui, |ui| {
|
|
|
|
self.show_menu(ctx, ui);
|
|
|
|
});
|
|
|
|
});
|
2024-11-28 17:39:08 +00:00
|
|
|
let frame = Frame::central_panel(&ctx.style()).fill(Color32::BLACK);
|
|
|
|
CentralPanel::default().frame(frame).show(ctx, |ui| {
|
2024-12-01 04:14:01 +00:00
|
|
|
if let Some(screen) = self.screen.as_mut() {
|
2024-12-01 05:45:13 +00:00
|
|
|
screen.update(self.display_mode, self.colors);
|
2024-11-26 05:38:03 +00:00
|
|
|
ui.add(screen);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2024-11-28 15:27:18 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_destroy(&mut self) {
|
|
|
|
if self.sim_id == SimId::Player2 {
|
|
|
|
self.client.send_command(EmulatorCommand::StopSecondSim);
|
|
|
|
}
|
|
|
|
}
|
2024-11-26 05:38:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
trait UiExt {
|
|
|
|
fn selectable_button(&mut self, selected: bool, text: impl Into<WidgetText>) -> Response;
|
2024-12-01 04:14:01 +00:00
|
|
|
fn selectable_option<T: Eq>(
|
|
|
|
&mut self,
|
|
|
|
current_value: &mut T,
|
|
|
|
selected_value: T,
|
|
|
|
text: impl Into<WidgetText>,
|
|
|
|
) -> Response {
|
|
|
|
let response = self.selectable_button(*current_value == selected_value, text);
|
|
|
|
if response.clicked() {
|
|
|
|
*current_value = selected_value;
|
|
|
|
}
|
|
|
|
response
|
|
|
|
}
|
2024-12-01 05:45:13 +00:00
|
|
|
|
|
|
|
fn color_pair_button(&mut self, left: Color32, right: Color32) -> Response;
|
2024-11-26 05:38:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UiExt for Ui {
|
|
|
|
fn selectable_button(&mut self, selected: bool, text: impl Into<WidgetText>) -> 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)
|
|
|
|
}
|
2024-12-01 05:45:13 +00:00
|
|
|
|
|
|
|
fn color_pair_button(&mut self, left: Color32, right: Color32) -> Response {
|
|
|
|
let button_size = Vec2::new(60.0, 20.0);
|
|
|
|
let (rect, response) = self.allocate_at_least(button_size, Sense::click());
|
|
|
|
let center_x = rect.center().x;
|
|
|
|
let left_rect = rect.with_max_x(center_x);
|
|
|
|
self.painter().rect_filled(left_rect, 0.0, left);
|
|
|
|
let right_rect = rect.with_min_x(center_x);
|
|
|
|
self.painter().rect_filled(right_rect, 0.0, right);
|
|
|
|
|
|
|
|
let style = self.style().interact(&response);
|
|
|
|
self.painter().rect_stroke(rect, 0.0, style.fg_stroke);
|
|
|
|
response
|
|
|
|
}
|
2024-11-26 05:38:03 +00:00
|
|
|
}
|