This repository has been archived on 2026-08-27. You can view files and clone it, but cannot push or open issues or pull requests.
gopher64-netplay/src/ui/gui.rs
2025-01-09 18:39:39 +01:00

363 lines
14 KiB
Rust

use crate::device;
use crate::ui;
use eframe::egui;
pub struct GopherEguiApp {
configure_profile: bool,
profile_name: String,
controllers: Vec<String>,
selected_controller: [i32; 4],
selected_profile: [String; 4],
input_profiles: Vec<String>,
controller_enabled: [bool; 4],
upscale: bool,
emulate_vru: bool,
show_vru_dialog: bool,
vru_window_receiver: Option<std::sync::mpsc::Receiver<Vec<String>>>,
vru_word_notifier: Option<std::sync::mpsc::Sender<String>>,
vru_word_list: Vec<String>,
}
fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
let mut profiles = vec![];
for key in game_ui.config.input.input_profiles.keys() {
profiles.push((*key).clone())
}
profiles
}
fn get_controllers(game_ui: &ui::Ui) -> Vec<String> {
let mut controllers: Vec<String> = vec![];
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
for i in 0..num_joysticks {
controllers.push(joystick_subsystem.name_for_index(i).unwrap());
}
controllers
}
impl GopherEguiApp {
pub fn new(cc: &eframe::CreationContext<'_>) -> GopherEguiApp {
add_japanese_font(&cc.egui_ctx);
let game_ui = ui::Ui::new();
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
let mut guids: Vec<String> = vec![];
for i in 0..num_joysticks {
guids.push(joystick_subsystem.device_guid(i).unwrap().to_string());
}
let mut selected_controller = [-1, -1, -1, -1];
for (pos, item) in game_ui
.config
.input
.controller_assignment
.iter()
.enumerate()
{
if item.is_some() {
for (guid_pos, guid) in guids.iter().enumerate() {
if item.as_deref().unwrap() == *guid {
selected_controller[pos] = guid_pos as i32;
break;
}
}
}
}
GopherEguiApp {
configure_profile: false,
profile_name: "".to_string(),
selected_profile: game_ui.config.input.input_profile_binding.clone(),
selected_controller,
controllers: get_controllers(&game_ui),
input_profiles: get_input_profiles(&game_ui),
controller_enabled: game_ui.config.input.controller_enabled,
upscale: game_ui.config.video.upscale,
emulate_vru: game_ui.config.input.emulate_vru,
show_vru_dialog: false,
vru_window_receiver: None,
vru_word_notifier: None,
vru_word_list: Vec::new(),
}
}
}
fn save_config(
game_ui: &mut ui::Ui,
selected_controller: [i32; 4],
selected_profile: [String; 4],
controller_enabled: [bool; 4],
upscale: bool,
emulate_vru: bool,
) {
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
for (pos, item) in selected_controller.iter().enumerate() {
if *item != -1 {
game_ui.config.input.controller_assignment[pos] = Some(
joystick_subsystem
.device_guid(*item as u32)
.unwrap()
.to_string(),
);
} else {
game_ui.config.input.controller_assignment[pos] = None
}
}
game_ui.config.input.input_profile_binding = selected_profile;
game_ui.config.input.controller_enabled = controller_enabled;
game_ui.config.video.upscale = upscale;
game_ui.config.input.emulate_vru = emulate_vru;
}
impl Drop for GopherEguiApp {
fn drop(&mut self) {
let mut game_ui = ui::Ui::new();
save_config(
&mut game_ui,
self.selected_controller,
self.selected_profile.clone(),
self.controller_enabled,
self.upscale,
self.emulate_vru,
);
}
}
impl eframe::App for GopherEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if self.configure_profile {
egui::Window::new("Configure Input Profile")
// .open(&mut self.configure_profile)
.show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name: ");
ui.text_edit_singleline(&mut self.profile_name)
.labelled_by(name_label.id);
});
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = self.profile_name.clone();
execute(async {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name);
});
self.configure_profile = false;
if !self.profile_name.is_empty()
&& !self.input_profiles.contains(&self.profile_name)
{
self.input_profiles.push(self.profile_name.clone())
}
};
if ui.button("Close").clicked() {
self.configure_profile = false
};
});
});
}
egui::CentralPanel::default().show(ctx, |ui| {
if self.configure_profile {
ui.disable()
}
if ui.button("Open ROM").clicked() {
// Spawn dialog on main thread
let task = rfd::AsyncFileDialog::new().pick_file();
let selected_controller = self.selected_controller;
let selected_profile = self.selected_profile.clone();
let controller_enabled = self.controller_enabled;
let upscale = self.upscale;
let emulate_vru = self.emulate_vru;
let (vru_window_notifier, vru_window_receiver): (
std::sync::mpsc::Sender<Vec<String>>,
std::sync::mpsc::Receiver<Vec<String>>,
) = std::sync::mpsc::channel();
let (vru_word_notifier, vru_word_receiver): (
std::sync::mpsc::Sender<String>,
std::sync::mpsc::Receiver<String>,
) = std::sync::mpsc::channel();
if emulate_vru {
self.vru_window_receiver = Some(vru_window_receiver);
self.vru_word_notifier = Some(vru_word_notifier);
}
let gui_ctx = ctx.clone();
execute(async move {
let file = task.await;
if let Some(file) = file {
let running_file = dirs::cache_dir()
.unwrap()
.join("gopher64")
.join("game_running");
if running_file.exists() {
return;
}
let _ = std::fs::File::create(running_file.clone());
let mut device = device::Device::new();
save_config(
&mut device.ui,
selected_controller,
selected_profile,
controller_enabled,
upscale,
emulate_vru,
);
if emulate_vru {
device.vru.window_notifier = Some(vru_window_notifier);
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
}
device::run_game(std::path::Path::new(file.path()), &mut device, false);
let _ = std::fs::remove_file(running_file.clone());
}
});
}
if ui.button("Configure Input Profile").clicked() {
let running_file = dirs::cache_dir()
.unwrap()
.join("gopher64")
.join("game_running");
if running_file.exists() {
return;
}
self.configure_profile = true;
}
ui.add_space(32.0);
ui.label("Controller Config:");
egui::Grid::new("controller_config").show(ui, |ui| {
ui.label("Port");
ui.label("Enabled");
ui.label("Profile");
ui.label("Controller");
ui.end_row();
for i in 0..4 {
ui.label(format!("{}", i + 1));
ui.checkbox(&mut self.controller_enabled[i], "");
egui::ComboBox::from_id_salt(format!("profile-combo-{}", i))
.selected_text(self.selected_profile[i].clone())
.show_ui(ui, |ui| {
for j in 0..self.input_profiles.len() {
ui.selectable_value(
&mut self.selected_profile[i],
self.input_profiles[j].clone(),
self.input_profiles[j].clone(),
);
}
});
let controller_text = if self.selected_controller[i] == -1 {
"None".to_string()
} else {
self.controllers[self.selected_controller[i] as usize].clone()
};
egui::ComboBox::from_id_salt(format!("controller-combo-{}", i))
.selected_text(controller_text)
.show_ui(ui, |ui| {
ui.selectable_value(
&mut self.selected_controller[i],
-1,
"None".to_string(),
);
for j in 0..self.controllers.len() {
ui.selectable_value(
&mut self.selected_controller[i],
j as i32,
self.controllers[j].clone(),
);
}
});
ui.end_row();
}
});
ui.add_space(32.0);
ui.checkbox(&mut self.upscale, "High-Res Graphics");
ui.checkbox(
&mut self.emulate_vru,
"Emulate VRU (connects VRU to controller port 4)",
);
ui.add_space(32.0);
ui.label(format!("Version: {}", env!("CARGO_PKG_VERSION")));
});
if self.emulate_vru && self.vru_window_receiver.is_some() {
let result = self.vru_window_receiver.as_ref().unwrap().try_recv();
if result.is_ok() {
self.show_vru_dialog = true;
self.vru_word_list = result.unwrap();
}
}
if self.show_vru_dialog {
ctx.show_viewport_immediate(
egui::ViewportId::from_hash_of("vru_dialog"),
egui::ViewportBuilder::default()
.with_title("What would you like to say?")
.with_always_on_top(),
|ctx, class| {
assert!(
class == egui::ViewportClass::Immediate,
"This egui backend doesn't support multiple viewports"
);
egui::CentralPanel::default().show(ctx, |ui| {
egui::Grid::new("vru_words").show(ui, |ui| {
for (i, v) in self.vru_word_list.iter().enumerate() {
if i % 5 == 0 {
ui.end_row();
}
if ui.button((*v).to_string()).clicked() {
self.vru_word_notifier
.as_ref()
.unwrap()
.send(v.clone())
.unwrap();
self.show_vru_dialog = false;
}
}
});
});
if ctx.input(|i| i.viewport().close_requested()) {
self.vru_word_notifier
.as_ref()
.unwrap()
.send(String::from(""))
.unwrap();
self.show_vru_dialog = false;
}
},
);
}
}
}
fn execute<F: std::future::Future<Output = ()> + Send + 'static>(f: F) {
std::thread::spawn(move || futures::executor::block_on(f));
}
fn add_japanese_font(ctx: &egui::Context) {
ctx.add_font(eframe::epaint::text::FontInsert::new(
"japanese_font",
egui::FontData::from_static(include_bytes!("../../data/NotoSansJP-Regular.ttf")),
vec![
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Proportional,
priority: egui::epaint::text::FontPriority::Lowest,
},
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Monospace,
priority: egui::epaint::text::FontPriority::Lowest,
},
],
));
}