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::::default()), ) } #[derive(PartialEq)] enum FileType { Iwad, Pwad, Launcher, } struct RustDoomLauncher { launcher_manager: SingleManager, iwad_manager: SingleManager, pwad_manager: MultiManager, add_name: String, config_filename: PathBuf, config: Option, 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 { 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 = None; let mut add_pos: Option = 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 = None; let mut add_pos: Option = 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 = None; let mut add_pos: Option = None; for (pwad, pos, selected) in self.pwad_manager.iter_selectable_with_pos_and_selected() { ui.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 = 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(); } } } }); } }