381 lines
14 KiB
Rust
381 lines
14 KiB
Rust
use config::{default_save_filename, load_config, save_config, Config};
|
|
use eframe::egui;
|
|
use infos::{Difficulty, LauncherInfo, WadInfo};
|
|
use native_dialog::{MessageDialog, MessageType};
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
pub mod config;
|
|
pub mod infos;
|
|
|
|
fn main() -> Result<(), eframe::Error> {
|
|
let gui_options = eframe::NativeOptions {
|
|
..Default::default()
|
|
};
|
|
eframe::run_native(
|
|
"Rust Doom Launcher",
|
|
gui_options,
|
|
Box::new(|_cc| Box::<RustDoomLauncher>::default()),
|
|
)
|
|
}
|
|
|
|
struct RustDoomLauncher {
|
|
all_launchers: Vec<LauncherInfo>,
|
|
all_iwads: Vec<WadInfo>,
|
|
all_pwads: Vec<WadInfo>,
|
|
selected_launcher: Option<usize>,
|
|
selected_iwad: Option<usize>,
|
|
selected_pwads: Vec<usize>,
|
|
name: String,
|
|
config_filename: PathBuf,
|
|
config_file_loaded: bool,
|
|
warp_string: String,
|
|
difficulty: Difficulty,
|
|
fast_monsters: bool,
|
|
respawning_monsters: bool,
|
|
display_command: bool,
|
|
}
|
|
|
|
// Great name, I know
|
|
enum MyErrors {
|
|
NoLauncher,
|
|
NoIwad,
|
|
}
|
|
|
|
impl Default for RustDoomLauncher {
|
|
fn default() -> Self {
|
|
Self {
|
|
all_launchers: Vec::new(),
|
|
all_iwads: Vec::new(),
|
|
all_pwads: Vec::new(),
|
|
selected_launcher: None,
|
|
selected_iwad: None,
|
|
selected_pwads: Vec::new(),
|
|
name: "".to_string(),
|
|
config_filename: default_save_filename(),
|
|
config_file_loaded: false,
|
|
warp_string: String::new(),
|
|
difficulty: Difficulty::None,
|
|
fast_monsters: false,
|
|
respawning_monsters: false,
|
|
display_command: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RustDoomLauncher {
|
|
// There must be a better way than this - maybe for the RustDoomLauncher to
|
|
// have a config thing
|
|
fn get_config_file_stuff(&mut self) -> () {
|
|
let config = load_config(&self.config_filename).unwrap();
|
|
if let Some(iwads) = config.iwads {
|
|
for iwad in iwads {
|
|
self.all_iwads.push(iwad.clone());
|
|
}
|
|
}
|
|
if let Some(pwads) = config.pwads {
|
|
for pwad in pwads {
|
|
self.all_pwads.push(pwad.clone());
|
|
}
|
|
}
|
|
if let Some(launchers) = config.launchers {
|
|
for launcher in launchers {
|
|
self.all_launchers.push(launcher.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_launcher_and_command(&self, show_dialogs: bool) -> Result<(&str, Vec<&str>), MyErrors> {
|
|
let launcher = match self.selected_launcher {
|
|
Some(l) => self.all_launchers.get(l).unwrap(),
|
|
None => {
|
|
if show_dialogs {
|
|
MessageDialog::new()
|
|
.set_type(MessageType::Error)
|
|
.set_title("I can't let you do that")
|
|
.set_text("You didn't pick a launcher!\nPlease do that")
|
|
.show_alert()
|
|
.unwrap();
|
|
}
|
|
return Err(MyErrors::NoLauncher);
|
|
}
|
|
};
|
|
let iwad = match self.selected_iwad {
|
|
Some(i) => self.all_iwads.get(i).unwrap(),
|
|
None => {
|
|
if show_dialogs {
|
|
MessageDialog::new()
|
|
.set_type(MessageType::Error)
|
|
.set_title("I can't let you do that")
|
|
.set_text("You didn't pick an IWAD!\nPlease do that")
|
|
.show_alert()
|
|
.unwrap();
|
|
}
|
|
return Err(MyErrors::NoIwad);
|
|
}
|
|
};
|
|
let mut command = vec!["-iwad", iwad.path.to_str().unwrap()];
|
|
for pwad_index in &self.selected_pwads {
|
|
command.push("-file");
|
|
let pwad = self.all_pwads.get(*pwad_index).unwrap();
|
|
command.push(pwad.path.to_str().unwrap());
|
|
}
|
|
if self.difficulty != Difficulty::None {
|
|
command.push("-skill");
|
|
command.push(self.difficulty.flag_number());
|
|
}
|
|
if !self.warp_string.is_empty() {
|
|
command.push("-warp");
|
|
command.push(&self.warp_string);
|
|
}
|
|
if self.respawning_monsters {
|
|
command.push("-respawn");
|
|
}
|
|
Ok((launcher.path.to_str().unwrap(), command))
|
|
}
|
|
|
|
fn form_command_string(&self) -> String {
|
|
match self.get_launcher_and_command(false) {
|
|
Ok((launch, comms)) => {
|
|
let mut command_string = String::new();
|
|
// Feels like a bit of a hack, but it works I think
|
|
command_string.push_str(&Cow::from(launch).to_string());
|
|
for c in comms {
|
|
command_string.push_str(" \'");
|
|
command_string.push_str(c);
|
|
command_string.push('\'')
|
|
}
|
|
command_string
|
|
}
|
|
Err(MyErrors::NoLauncher) => "Error: No launcher".to_string(),
|
|
Err(MyErrors::NoIwad) => "Error: No IWAD".to_string(),
|
|
}
|
|
}
|
|
|
|
fn launch_doom(&self) -> () {
|
|
if let Ok((launcher, command)) = self.get_launcher_and_command(true) {
|
|
// Note to self, don't hit launch button when running from emacs
|
|
Command::new(launcher).args(command).spawn();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl eframe::App for RustDoomLauncher {
|
|
fn on_close_event(&mut self) -> bool {
|
|
let config = Config {
|
|
iwads: Some(self.all_iwads.clone()),
|
|
pwads: Some(self.all_pwads.clone()),
|
|
launchers: Some(self.all_launchers.clone()),
|
|
};
|
|
save_config(&self.config_filename, &config).unwrap();
|
|
true
|
|
}
|
|
|
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
|
if self.config_file_loaded == false {
|
|
self.get_config_file_stuff();
|
|
self.config_file_loaded = true;
|
|
}
|
|
egui::containers::panel::CentralPanel::default().show(ctx, |ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.heading("RustDoomLauncher");
|
|
if ui.button("PLAY SOME DOOM!").clicked() {
|
|
self.launch_doom();
|
|
}
|
|
});
|
|
ui.horizontal_wrapped(|ui| {
|
|
ui.vertical(|ui| {
|
|
ui.label("Launchers");
|
|
for (pos, launcher) in self.all_launchers.iter().enumerate() {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(
|
|
self.selected_launcher.is_some()
|
|
&& *self.selected_launcher.as_ref().unwrap() == pos,
|
|
&launcher.name,
|
|
))
|
|
.clicked()
|
|
{
|
|
self.selected_launcher = Some(pos);
|
|
}
|
|
}
|
|
});
|
|
ui.separator();
|
|
ui.vertical(|ui| {
|
|
ui.set_min_width(100.0);
|
|
ui.label("IWADs");
|
|
for (pos, iwad) in self.all_iwads.iter().enumerate() {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(
|
|
self.selected_iwad.is_some()
|
|
&& *self.selected_iwad.as_ref().unwrap() == pos,
|
|
&iwad.name,
|
|
))
|
|
.clicked()
|
|
{
|
|
self.selected_iwad = Some(pos);
|
|
}
|
|
}
|
|
});
|
|
ui.separator();
|
|
ui.vertical(|ui| {
|
|
ui.set_min_width(100.0);
|
|
ui.label("PWADs and Mods");
|
|
for (pos, pwad) in self.all_pwads.iter().enumerate() {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(
|
|
self.selected_pwads.contains(&pos),
|
|
&pwad.name,
|
|
))
|
|
.clicked()
|
|
{
|
|
self.selected_pwads.push(pos);
|
|
}
|
|
}
|
|
});
|
|
ui.separator();
|
|
ui.vertical(|ui| {
|
|
ui.label("Add WADs etc");
|
|
ui.set_max_width(180.0);
|
|
ui.horizontal(|ui| {
|
|
let name_label = ui.label("Name:");
|
|
ui.text_edit_singleline(&mut self.name)
|
|
.labelled_by(name_label.id);
|
|
});
|
|
// It kind of feels like the right place to use a closure - unsure whether I need
|
|
// to pass both of these vaules in, or what the story is here.
|
|
let get_name = |path: &PathBuf, name: &String| {
|
|
if self.name.is_empty() {
|
|
// Check these unwraps perhaps? Unsure whether the FileDialog can actually
|
|
// return something that isn't a file
|
|
path.file_name().unwrap().to_str().unwrap().to_string()
|
|
} else {
|
|
name.clone()
|
|
}
|
|
};
|
|
|
|
if ui.button("Add Launcher").clicked() {
|
|
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
|
self.all_launchers.push(LauncherInfo {
|
|
name: get_name(&path, &self.name),
|
|
path,
|
|
});
|
|
}
|
|
}
|
|
if ui.button("Add IWAD").clicked() {
|
|
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
|
self.all_iwads.push(WadInfo {
|
|
name: get_name(&path, &self.name),
|
|
path,
|
|
});
|
|
}
|
|
}
|
|
if ui.button("Add PWAD or Mod").clicked() {
|
|
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
|
self.all_pwads.push(WadInfo {
|
|
name: get_name(&path, &self.name),
|
|
path,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
ui.separator();
|
|
ui.horizontal_wrapped(|ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.set_max_width(180.0);
|
|
let warp_label = ui.label("warp");
|
|
ui.text_edit_singleline(&mut self.warp_string)
|
|
.labelled_by(warp_label.id);
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.label("difficulty");
|
|
eframe::egui::ComboBox::new("difficulty", "")
|
|
.selected_text(format!("{}", self.difficulty))
|
|
.show_ui(ui, |ui| {
|
|
for diff in Difficulty::iterator() {
|
|
ui.selectable_value(&mut self.difficulty, *diff, diff.to_string());
|
|
}
|
|
});
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.label("fast");
|
|
ui.checkbox(&mut self.fast_monsters, "");
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.label("respawn");
|
|
ui.checkbox(&mut self.respawning_monsters, "");
|
|
});
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.label("Command");
|
|
ui.checkbox(&mut self.display_command, "");
|
|
});
|
|
ui.horizontal_wrapped(|ui| {
|
|
// I don't actually think using SelectableLabel is correct here -
|
|
// but it'll at least do the highlighting when hovered nicely
|
|
if let Some(l) = self.selected_launcher {
|
|
if let Some(iwad) = self.all_launchers.get(l) {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(false, &iwad.name))
|
|
.clicked()
|
|
{
|
|
self.selected_launcher = None;
|
|
}
|
|
} else {
|
|
self.selected_launcher = None;
|
|
}
|
|
} else {
|
|
ui.label("Select a launcher plz");
|
|
}
|
|
if let Some(i) = self.selected_iwad {
|
|
if let Some(iwad) = self.all_iwads.get(i) {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(false, &iwad.name))
|
|
.clicked()
|
|
{
|
|
self.selected_iwad = None;
|
|
}
|
|
} else {
|
|
self.selected_iwad = None;
|
|
}
|
|
} else {
|
|
ui.label("Select an iwad plz");
|
|
}
|
|
// This feels ver much more C-like, but I think it works?
|
|
// Probably should have a little bit more checking around the unwrap calls
|
|
let mut pos = 0_usize;
|
|
while pos < self.selected_pwads.len() {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(
|
|
false,
|
|
&self
|
|
.all_pwads
|
|
.get(*self.selected_pwads.get(pos).unwrap())
|
|
.unwrap()
|
|
.name,
|
|
))
|
|
.clicked()
|
|
{
|
|
self.selected_pwads.remove(pos);
|
|
continue;
|
|
}
|
|
pos += 1;
|
|
}
|
|
});
|
|
if self.display_command {
|
|
ui.horizontal(|ui| {
|
|
let mut text = self.form_command_string();
|
|
let window_size = frame.info().window_info.size;
|
|
ui.add(
|
|
egui::TextEdit::multiline(&mut text).desired_width(window_size[0] * 0.9),
|
|
);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|