diff --git a/src/infos.rs b/src/infos.rs index a4b93e6..2f29612 100644 --- a/src/infos.rs +++ b/src/infos.rs @@ -3,10 +3,112 @@ use std::fmt; use std::path::PathBuf; use std::slice::Iter; -// TODO - write some impl stuff in here so the main GUI stuff -// is a little bit more decoupled from this. I think a Display -// and perhaps the path to string conversion too could be -// removed from the GUI part +pub struct CommandManager { + pub launcher: Option, + pub iwad: Option, + pub pwads: Vec, + pub warp: String, + pub difficulty: Option, + pub fast_monsters: bool, + pub respawning_monsters: bool, + pub command_string: String, +} + +pub enum CommandErrors { + NoLauncher, + NoIwad, +} + +impl CommandManager { + pub fn new() -> Self { + Self { + launcher: None, + iwad: None, + pwads: Vec::new(), + warp: "".to_string(), + difficulty: None, + fast_monsters: false, + respawning_monsters: false, + command_string: "".to_string(), + } + } + pub fn remove_iwad(&mut self) { + self.iwad = None; + self.generate_command_str(); + } + pub fn add_iwad(&mut self, iwad: &WadInfo) { + self.iwad = Some(iwad.path.clone()); + self.generate_command_str(); + } + pub fn add_pwads(&mut self, pwads: &Vec<&WadInfo>) { + self.pwads = Vec::new(); + for pwad in pwads { + self.pwads.push(pwad.path.clone()); + self.generate_command_str(); + } + } + pub fn add_launcher(&mut self, launcher: &LauncherInfo) { + self.launcher = Some(launcher.path.clone()); + self.generate_command_str(); + } + pub fn remove_launcher(&mut self) { + self.launcher = None; + self.generate_command_str(); + } + pub fn generate_command(&self) -> Result<(String, Vec), CommandErrors> { + let launcher = if let Some(launcher) = &self.launcher { + launcher + } else { + return Err(CommandErrors::NoLauncher); + }; + let iwad = if let Some(iwad) = &self.iwad { + iwad + } else { + return Err(CommandErrors::NoIwad); + }; + let mut command = vec!["-iwad".to_string(), iwad.clone().into_os_string().into_string().unwrap()]; + for pwad in &self.pwads { + command.push("-file".to_string()); + command.push(pwad.clone().into_os_string().into_string().unwrap()); + } + if let Some(d) = self.difficulty { + command.push("-skill".to_string()); + command.push(d.flag_number().to_string()); + } + if !self.warp.is_empty() { + command.push("-warp".to_string()); + command.push(self.warp.to_string()); + } + if self.respawning_monsters { + command.push("-respawn".to_string()); + } + if self.fast_monsters { + command.push("-fast".to_string()); + } + Ok((launcher.to_str().unwrap().to_string(), command)) + } + pub fn generate_command_str(&mut self) -> Result<(), CommandErrors> { + match self.generate_command() { + Err(e) => { + self.command_string = "plz add ".to_string(); + match e { + CommandErrors::NoIwad => self.command_string.push_str("an iwad"), + CommandErrors::NoLauncher => self.command_string.push_str("a launcher"), + } + }, + Ok((launcher, rest)) => { + let mut command = "".to_string(); + // Feels like a bit of a hack, but it works I think + command.push_str(&format!(" '{}'", launcher)); + for c in rest { + command.push_str(&format!(" '{}'", c)); + } + self.command_string = command; + } + } + Ok(()) + } +} #[derive(Serialize, Deserialize, Debug, Clone)] pub struct WadInfo { @@ -20,28 +122,29 @@ pub struct LauncherInfo { pub name: String, } -//trait Manager {} - pub struct MultiManager { pub selectable: Vec, pub current_selected: Vec, } -impl MultiManager { +impl MultiManager { pub fn new() -> Self { Self { selectable: Vec::new(), current_selected: Vec::new(), } } - pub fn add(&mut self, item: T) { - self.selectable.push(item); + pub fn add(&mut self, item: &T) { + self.selectable.push(item.clone()); } pub fn get(&self, index: usize) -> Option<(&T, bool)> { if index > self.selectable.len() { None } else { - Some((self.selectable.get(index).unwrap(), self.current_selected.contains(&index))) + Some(( + self.selectable.get(index).unwrap(), + self.current_selected.contains(&index), + )) } } pub fn get_current(&self) -> Vec<&T> { @@ -52,7 +155,7 @@ impl MultiManager { } current_selected_items } - pub fn add_current(&mut self, index: usize) { + pub fn set_current(&mut self, index: usize) { assert!(index < self.selectable.len()); self.current_selected.push(index); } @@ -63,6 +166,21 @@ impl MultiManager { pub fn remove_selectable(&mut self, index: usize) { assert!(index < self.selectable.len()); self.selectable.remove(index); + let mut new_selected: Vec = Vec::new(); + for s in &self.current_selected { + println!("{}", s); + if *s == index { + continue + } else if *s > index { + new_selected.push(s - 1); + } else { + new_selected.push(*s); + } + } + self.current_selected = new_selected; + } + pub fn is_currently_selected(self, index: usize) -> bool { + self.current_selected.contains(&index) } pub fn iter_selectable_with_pos_and_selected( &mut self, @@ -79,21 +197,24 @@ pub struct SingleManager { pub current_selected: Option, } -impl SingleManager { +impl SingleManager { pub fn new() -> Self { Self { selectable: Vec::new(), current_selected: None, } } - pub fn add(&mut self, item: T) { - self.selectable.push(item); + pub fn add(&mut self, item: &T) { + self.selectable.push(item.clone()); } pub fn get(&self, index: usize) -> Option<(&T, bool)> { if index > self.selectable.len() { None } else { - Some((self.selectable.get(index).unwrap(), index == self.current_selected.unwrap_or(std::usize::MAX))) + Some(( + self.selectable.get(index).unwrap(), + index == self.current_selected.unwrap_or(std::usize::MAX), + )) } } pub fn get_current(&self) -> Option<&T> { @@ -112,6 +233,9 @@ impl SingleManager { pub fn remove_selectable(&mut self, index: usize) { assert!(index < self.selectable.len()); self.selectable.remove(index); + if self.current_selected.is_some() && index == self.current_selected.unwrap() { + self.current_selected = None; + } } pub fn iter_selectable_with_pos_and_selected( &mut self, diff --git a/src/main.rs b/src/main.rs index 8539d1e..46a3186 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use config::{default_save_filename, load_config, save_config, Config}; use eframe::egui; use eframe::egui::Color32; -use infos::{Difficulty, LauncherInfo, MultiManager, SingleManager, WadInfo}; +use infos::{Difficulty, LauncherInfo, MultiManager, SingleManager, WadInfo, CommandManager, CommandErrors}; use native_dialog::{MessageDialog, MessageType}; use std::path::PathBuf; use std::process::Command; @@ -26,19 +26,15 @@ enum FileType { Pwad, Launcher, } -// Great name, I know -enum MyErrors { - NoLauncher, - NoIwad, -} struct RustDoomLauncher { launcher_manager: SingleManager, iwad_manager: SingleManager, pwad_manager: MultiManager, - name: String, + add_name: String, config_filename: PathBuf, config_file_loaded: bool, + command_manager: CommandManager, warp_string: String, difficulty: Difficulty, fast_monsters: bool, @@ -55,7 +51,8 @@ impl Default for RustDoomLauncher { launcher_manager: SingleManager::new(), iwad_manager: SingleManager::new(), pwad_manager: MultiManager::new(), - name: "".to_string(), + command_manager: CommandManager::new(), + add_name: "".to_string(), config_filename: default_save_filename(), config_file_loaded: false, warp_string: String::new(), @@ -77,96 +74,53 @@ impl RustDoomLauncher { let config = load_config(&self.config_filename).unwrap(); if let Some(iwads) = config.iwads { for iwad in iwads { - self.iwad_manager.add(iwad); + self.iwad_manager.add(&iwad); } } if let Some(pwads) = config.pwads { for pwad in pwads { - self.pwad_manager.add(pwad.clone()); + self.pwad_manager.add(&pwad.clone()); } } if let Some(launchers) = config.launchers { for launcher in launchers { - self.launcher_manager.add(launcher); + self.launcher_manager.add(&launcher); } } } - fn get_launcher_and_command(&self, show_dialogs: bool) -> Result<(&str, Vec<&str>), MyErrors> { - let (launcher, iwad) = match self.launcher_and_iwad() { - Err(e) => { - let text = match e { - MyErrors::NoIwad => "an IWAD", - MyErrors::NoLauncher => "a launcher", - }; - if show_dialogs { - 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(); - } - return Err(e); - } - Ok((l, i)) => (l, i), - }; - let mut command = vec!["-iwad", iwad.path.to_str().unwrap()]; - for pwad in &self.pwad_manager.get_current() { - command.push("-file"); - 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"); - } - if self.fast_monsters { - command.push("-fast"); - } - 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(&format!(" '{}'", launch)); - for c in comms { - command_string.push_str(&format!(" '{}'", c)); - } - 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(); + 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 + Command::new(launcher).args(command).spawn(); + } } } - fn launcher_and_iwad(&self) -> Result<(&LauncherInfo, &WadInfo), MyErrors> { + fn launcher_and_iwad(&self) -> Result<(&LauncherInfo, &WadInfo), CommandErrors> { let launcher = match self.launcher_manager.get_current() { Some(l) => l, None => { - return Err(MyErrors::NoLauncher); + return Err(CommandErrors::NoLauncher); } }; let iwad = match self.iwad_manager.get_current() { Some(iwad) => iwad, None => { - return Err(MyErrors::NoIwad); + return Err(CommandErrors::NoIwad); } }; Ok((launcher, iwad)) @@ -230,9 +184,13 @@ impl eframe::App for RustDoomLauncher { // 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(); @@ -257,9 +215,13 @@ impl eframe::App for RustDoomLauncher { } 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(); @@ -283,9 +245,11 @@ impl eframe::App for RustDoomLauncher { } 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.add_current(ap); + self.pwad_manager.set_current(ap); + self.command_manager.add_pwads(&self.pwad_manager.get_current()) } }); }); @@ -325,12 +289,10 @@ impl eframe::App for RustDoomLauncher { 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.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"); @@ -341,7 +303,8 @@ impl eframe::App for RustDoomLauncher { .add(egui::SelectableLabel::new(false, &iwad.name)) .clicked() { - self.launcher_manager.remove_current(); + self.iwad_manager.remove_current(); + self.command_manager.remove_iwad(); } } else { ui.label("Select an iwad plz"); @@ -360,14 +323,15 @@ impl eframe::App for RustDoomLauncher { } 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.form_command_string(); + 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] * 0.9), + egui::TextEdit::multiline(&mut text).desired_width(window_size[0]), ); }); } @@ -378,7 +342,7 @@ impl eframe::App for RustDoomLauncher { .show(ctx, |ui| { ui.horizontal(|ui| { let name_label = ui.label("Name"); - ui.text_edit_singleline(&mut self.name) + ui.text_edit_singleline(&mut self.add_name) .labelled_by(name_label.id); }); ui.horizontal(|ui| { @@ -427,25 +391,25 @@ impl eframe::App for RustDoomLauncher { } }); if ui.button("Add!").clicked() { - if self.name.is_empty() || self.selected_file_path.as_os_str().is_empty() { + if self.add_name.is_empty() || self.selected_file_path.as_os_str().is_empty() { return; } match self.selected_file_type { FileType::Iwad => { - self.iwad_manager.add(WadInfo { - name: self.name.clone(), + self.iwad_manager.add(&WadInfo { + name: self.add_name.clone(), path: self.selected_file_path.clone(), }); } FileType::Pwad => { - self.pwad_manager.add(WadInfo { - name: self.name.clone(), + self.pwad_manager.add(&WadInfo { + name: self.add_name.clone(), path: self.selected_file_path.clone(), }); } FileType::Launcher => { - self.launcher_manager.add(LauncherInfo { - name: self.name.clone(), + self.launcher_manager.add(&LauncherInfo { + name: self.add_name.clone(), path: self.selected_file_path.clone(), }); }