500 lines
20 KiB
Rust
500 lines
20 KiB
Rust
use config::{default_config_filename, load_config, Config};
|
|
use eframe::egui;
|
|
use eframe::egui::Color32;
|
|
use infos::{
|
|
CommandErrors, CommandManager, Difficulty, LauncherInfo, MultiManager, SingleManager, WadInfo,
|
|
};
|
|
use native_dialog::{MessageDialog, MessageType};
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
use wad::get_summary;
|
|
use walkdir::{DirEntry, WalkDir};
|
|
|
|
pub mod config;
|
|
pub mod infos;
|
|
pub mod wad;
|
|
|
|
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()),
|
|
)
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
enum FileType {
|
|
Iwad,
|
|
Pwad,
|
|
Launcher,
|
|
}
|
|
|
|
struct RustDoomLauncher {
|
|
launcher_manager: SingleManager<LauncherInfo>,
|
|
iwad_manager: SingleManager<WadInfo>,
|
|
pwad_manager: MultiManager<WadInfo>,
|
|
add_name: String,
|
|
config_filename: PathBuf,
|
|
config: Option<Config>,
|
|
config_tried: bool,
|
|
command_manager: CommandManager,
|
|
display_command: bool,
|
|
add_stuff_window_displayed: bool,
|
|
selected_file_type: FileType,
|
|
selected_file_path: PathBuf,
|
|
}
|
|
|
|
impl Default for RustDoomLauncher {
|
|
fn default() -> Self {
|
|
Self {
|
|
launcher_manager: SingleManager::new(),
|
|
iwad_manager: SingleManager::new(),
|
|
pwad_manager: MultiManager::new(),
|
|
command_manager: CommandManager::default(),
|
|
add_name: "".to_string(),
|
|
config_filename: default_config_filename(),
|
|
config: None, // TODO: put the config_filename in the config stuct - or something
|
|
config_tried: false,
|
|
display_command: false,
|
|
add_stuff_window_displayed: false,
|
|
selected_file_type: FileType::Pwad,
|
|
selected_file_path: PathBuf::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_wad_file(entry: &DirEntry) -> bool {
|
|
let filename = entry.file_name().to_str().unwrap().to_lowercase();
|
|
println!("{} - > {}", filename, filename.ends_with("wad"));
|
|
filename.ends_with("wad") || filename.ends_with("pk3")
|
|
}
|
|
|
|
fn get_wads_in_folder(path: &PathBuf) -> Vec<WadInfo> {
|
|
let mut wads = Vec::new();
|
|
for entry in WalkDir::new(path).max_depth(2) {
|
|
let entry = entry.unwrap();
|
|
if is_wad_file(&entry) {
|
|
|
|
wads.push(WadInfo {
|
|
name: format!("{}", entry.path().file_name().unwrap().to_str().unwrap()),
|
|
path: entry.path().to_path_buf(),
|
|
info_text: None,
|
|
})
|
|
}
|
|
}
|
|
wads
|
|
}
|
|
|
|
impl RustDoomLauncher {
|
|
fn get_config_file_stuff(&mut self) {
|
|
// TODO - Throw some kind of error if a config file isn't found and/or missing iwad folder
|
|
println!("{:?}", self.config_filename);
|
|
match load_config(&self.config_filename) {
|
|
Ok(c) => {
|
|
println!("{:?}", c);
|
|
if let Some(launchers) = &c.launchers {
|
|
for launcher in launchers {
|
|
self.launcher_manager.add(&launcher);
|
|
}
|
|
}
|
|
for iwad in get_wads_in_folder(&c.iwads_folder) {
|
|
self.iwad_manager.add(&iwad);
|
|
}
|
|
if let Some(pwad_folder) = &c.pwads_folder {
|
|
for pwad in get_wads_in_folder(pwad_folder) {
|
|
self.pwad_manager.add(&pwad)
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("{:?}", e);
|
|
}
|
|
};
|
|
}
|
|
|
|
fn launch_doom(&self) {
|
|
match self.command_manager.generate_command() {
|
|
Err(e) => {
|
|
let text = match e {
|
|
CommandErrors::NoIwad => "an IWAD",
|
|
CommandErrors::NoLauncher => "a launcher",
|
|
};
|
|
MessageDialog::new()
|
|
.set_type(MessageType::Error)
|
|
.set_title("I can't let you do that")
|
|
.set_text(&format!("You didn't pick {}!\nPlease do that", text))
|
|
.show_alert()
|
|
.unwrap();
|
|
}
|
|
Ok((launcher, command)) => {
|
|
// Note to self, don't hit launch button when running from emacs
|
|
// if you do, close it and hold down C+g
|
|
let result = Command::new(launcher).args(command).spawn();
|
|
match result {
|
|
Err(e) => panic!("{}", e),
|
|
Ok(_o) => (),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn launcher_and_iwad(&self) -> Result<(&LauncherInfo, &WadInfo), CommandErrors> {
|
|
let launcher = match self.launcher_manager.get_current() {
|
|
Some(l) => l,
|
|
None => {
|
|
return Err(CommandErrors::NoLauncher);
|
|
}
|
|
};
|
|
let iwad = match self.iwad_manager.get_current() {
|
|
Some(iwad) => iwad,
|
|
None => {
|
|
return Err(CommandErrors::NoIwad);
|
|
}
|
|
};
|
|
Ok((launcher, iwad))
|
|
}
|
|
}
|
|
|
|
impl eframe::App for RustDoomLauncher {
|
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
|
if !self.config_tried {
|
|
self.get_config_file_stuff();
|
|
self.config_tried = true;
|
|
};
|
|
egui::containers::panel::CentralPanel::default().show(ctx, |ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.heading("RustDoomLauncher");
|
|
let button = match self.launcher_and_iwad() {
|
|
Err(_) => egui::Button::new("PLAY SOME DOOM!"),
|
|
Ok((_, _)) => egui::Button::new("PLAY SOME DOOM!").fill(Color32::DARK_GREEN),
|
|
};
|
|
if ui.add(button).clicked() {
|
|
self.launch_doom();
|
|
}
|
|
if ui.button("Add WAD or Launcher").clicked() {
|
|
self.add_stuff_window_displayed = true;
|
|
}
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.vertical(|ui| {
|
|
ui.set_max_width(100.0);
|
|
ui.label("Launchers");
|
|
let mut remove_pos: Option<usize> = None;
|
|
let mut add_pos: Option<usize> = None;
|
|
for (launcher, pos, selected) in self
|
|
.launcher_manager
|
|
.iter_selectable_with_pos_and_selected()
|
|
{
|
|
ui.horizontal(|ui| {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(selected, &launcher.name))
|
|
.clicked()
|
|
{
|
|
add_pos = Some(pos);
|
|
}
|
|
if ui.button("❌").clicked() {
|
|
remove_pos = Some(pos);
|
|
}
|
|
});
|
|
}
|
|
// I'm unsure whether there's a better way to do this.
|
|
// The iterator call is a mutable borrow - thus compiler doesn't let me
|
|
// do a mutable thing in the middle of that, which I guess is fair enough,
|
|
// but I would like to break out of the loop as soon as that occurs...
|
|
// Completely unsure
|
|
if let Some(rp) = remove_pos {
|
|
self.launcher_manager.remove_selectable(rp);
|
|
if self.launcher_manager.get_current().is_none() {
|
|
self.command_manager.remove_launcher();
|
|
}
|
|
}
|
|
if let Some(ap) = add_pos {
|
|
self.launcher_manager.set_current(ap);
|
|
self.command_manager
|
|
.add_launcher(self.launcher_manager.get_current().unwrap())
|
|
}
|
|
ui.separator();
|
|
ui.label("IWADs");
|
|
let mut remove_pos: Option<usize> = None;
|
|
let mut add_pos: Option<usize> = None;
|
|
for (iwad, pos, selected) in
|
|
self.iwad_manager.iter_selectable_with_pos_and_selected()
|
|
{
|
|
ui.horizontal(|ui| {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(selected, &iwad.name))
|
|
.clicked()
|
|
{
|
|
add_pos = Some(pos);
|
|
}
|
|
if ui.button("❌").clicked() {
|
|
remove_pos = Some(pos);
|
|
}
|
|
});
|
|
}
|
|
if let Some(rp) = remove_pos {
|
|
self.iwad_manager.remove_selectable(rp);
|
|
if self.launcher_manager.get_current().is_none() {
|
|
self.command_manager.remove_launcher();
|
|
}
|
|
}
|
|
if let Some(ap) = add_pos {
|
|
self.iwad_manager.set_current(ap);
|
|
self.command_manager
|
|
.add_iwad(self.iwad_manager.get_current().unwrap());
|
|
}
|
|
});
|
|
ui.separator();
|
|
ui.vertical(|ui| {
|
|
ui.set_min_width(100.0);
|
|
ui.label("PWADs");
|
|
let mut remove_pos: Option<usize> = None;
|
|
let mut add_pos: Option<usize> = None;
|
|
ui.columns(2, |columns| {
|
|
for (i, (pwad, pos, selected)) in
|
|
self.pwad_manager.iter_selectable_with_pos_and_selected().enumerate()
|
|
{
|
|
columns[i%2].horizontal(|ui| {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(selected, &pwad.name))
|
|
.clicked()
|
|
{
|
|
add_pos = Some(pos);
|
|
}
|
|
if ui.button("❌").clicked() {
|
|
remove_pos = Some(pos);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
/*
|
|
if let Some(rp) = remove_pos {
|
|
self.pwad_manager.remove_selectable(rp);
|
|
self.command_manager
|
|
.add_pwads(&self.pwad_manager.get_current())
|
|
}
|
|
*/
|
|
if let Some(ap) = add_pos {
|
|
self.pwad_manager.set_current(ap);
|
|
self.command_manager
|
|
.add_pwads(&self.pwad_manager.get_current())
|
|
}
|
|
|
|
});
|
|
});
|
|
ui.separator();
|
|
ui.horizontal_wrapped(|ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.set_max_width(180.0);
|
|
let warp_label = ui.label("warp");
|
|
let r = ui
|
|
.text_edit_singleline(&mut self.command_manager.warp)
|
|
.labelled_by(warp_label.id);
|
|
if r.changed() {
|
|
self.command_manager.update_command();
|
|
}
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.label("difficulty");
|
|
eframe::egui::ComboBox::new("difficulty", "")
|
|
.selected_text(format!("{}", self.command_manager.difficulty))
|
|
.width(140.0)
|
|
.show_ui(ui, |ui| {
|
|
for diff in Difficulty::iterator() {
|
|
let r = ui.selectable_value(
|
|
&mut self.command_manager.difficulty,
|
|
*diff,
|
|
diff.to_string(),
|
|
);
|
|
if r.clicked() {
|
|
self.command_manager.update_command();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.label("fast");
|
|
let r = ui.add(egui::Checkbox::without_text(
|
|
&mut self.command_manager.fast_monsters,
|
|
));
|
|
if r.clicked() {
|
|
self.command_manager.update_command();
|
|
}
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.label("respawn");
|
|
let r = ui.add(egui::Checkbox::without_text(
|
|
&mut self.command_manager.respawning_monsters,
|
|
));
|
|
if r.clicked() {
|
|
self.command_manager.update_command();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
ui.separator();
|
|
ui.horizontal(|ui| {
|
|
ui.label("Command");
|
|
ui.add(egui::Checkbox::without_text(&mut self.display_command));
|
|
});
|
|
ui.horizontal_wrapped(|ui| {
|
|
if let Some(l) = self.launcher_manager.get_current() {
|
|
if ui.add(egui::SelectableLabel::new(false, &l.name)).clicked() {
|
|
self.launcher_manager.remove_current();
|
|
self.command_manager.remove_launcher();
|
|
}
|
|
} else {
|
|
ui.label("Select a launcher plz");
|
|
}
|
|
|
|
if let Some(iwad) = self.iwad_manager.get_current() {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(false, &iwad.name))
|
|
.clicked()
|
|
{
|
|
self.iwad_manager.remove_current();
|
|
self.command_manager.remove_iwad();
|
|
}
|
|
} else {
|
|
ui.label("Select an iwad plz");
|
|
}
|
|
let mut remove: Option<usize> = None;
|
|
for (pos, pwad) in self.pwad_manager.get_current().iter().enumerate() {
|
|
if ui
|
|
.add(egui::SelectableLabel::new(false, &pwad.name))
|
|
.clicked()
|
|
{
|
|
remove = Some(pos);
|
|
break;
|
|
}
|
|
}
|
|
if let Some(r) = remove {
|
|
self.pwad_manager.remove_current(r);
|
|
self.command_manager
|
|
.add_pwads(&self.pwad_manager.get_current())
|
|
}
|
|
});
|
|
if self.display_command {
|
|
ui.horizontal(|ui| {
|
|
let mut text = self.command_manager.command_string.clone();
|
|
let window_size = frame.info().window_info.size;
|
|
ui.add(egui::TextEdit::multiline(&mut text).desired_width(window_size[0]));
|
|
});
|
|
}
|
|
});
|
|
|
|
egui::Window::new("Add WAD or Launcher")
|
|
.open(&mut self.add_stuff_window_displayed)
|
|
.show(ctx, |ui| {
|
|
ui.horizontal(|ui| {
|
|
let name_label = ui.label("Name");
|
|
ui.text_edit_singleline(&mut self.add_name)
|
|
.labelled_by(name_label.id);
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.label("Path");
|
|
ui.label(
|
|
self.selected_file_path
|
|
.clone()
|
|
.into_os_string()
|
|
.into_string()
|
|
.unwrap(),
|
|
);
|
|
if ui.button("Find").clicked() {
|
|
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
|
self.selected_file_path = path;
|
|
}
|
|
}
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.label("What sort of file is it?");
|
|
if ui
|
|
.add(egui::SelectableLabel::new(
|
|
self.selected_file_type == FileType::Launcher,
|
|
"Launcher",
|
|
))
|
|
.clicked()
|
|
{
|
|
self.selected_file_type = FileType::Launcher
|
|
}
|
|
if ui
|
|
.add(egui::SelectableLabel::new(
|
|
self.selected_file_type == FileType::Iwad,
|
|
"IWAD",
|
|
))
|
|
.clicked()
|
|
{
|
|
self.selected_file_type = FileType::Iwad
|
|
}
|
|
if ui
|
|
.add(egui::SelectableLabel::new(
|
|
self.selected_file_type == FileType::Pwad,
|
|
"PWAD",
|
|
))
|
|
.clicked()
|
|
{
|
|
self.selected_file_type = FileType::Pwad
|
|
}
|
|
});
|
|
if ui.button("Add!").clicked() {
|
|
if self.add_name.is_empty() || self.selected_file_path.as_os_str().is_empty() {
|
|
return;
|
|
}
|
|
let info_text = get_summary(&self.selected_file_path);
|
|
match self.selected_file_type {
|
|
FileType::Iwad => {
|
|
self.iwad_manager.add(&WadInfo {
|
|
name: self.add_name.clone(),
|
|
path: self.selected_file_path.clone(),
|
|
info_text: Some(
|
|
"I'm sure you know what's in that bloody IWAD!".to_string(),
|
|
),
|
|
});
|
|
}
|
|
FileType::Pwad => {
|
|
self.pwad_manager.add(&WadInfo {
|
|
name: self.add_name.clone(),
|
|
path: self.selected_file_path.clone(),
|
|
info_text: info_text,
|
|
});
|
|
}
|
|
FileType::Launcher => {
|
|
self.launcher_manager.add(&LauncherInfo {
|
|
name: self.add_name.clone(),
|
|
path: self.selected_file_path.clone(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
ctx.input(|i| {
|
|
if !i.raw.dropped_files.is_empty() {
|
|
let dropped_files = i.raw.dropped_files.clone();
|
|
match dropped_files.len() {
|
|
1 => {
|
|
self.selected_file_path = dropped_files[0].path.clone().unwrap();
|
|
self.add_stuff_window_displayed = true;
|
|
}
|
|
_ => {
|
|
MessageDialog::new()
|
|
.set_type(MessageType::Error)
|
|
.set_title("I can't let you do that")
|
|
.set_text(&format!("Multiple files not supported"))
|
|
.show_alert()
|
|
.unwrap();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|