Added a command manager

This commit is contained in:
2023-06-18 20:42:37 +01:00
parent bf41c9d70a
commit 849902ce84
2 changed files with 192 additions and 104 deletions

View File

@@ -3,10 +3,112 @@ use std::fmt;
use std::path::PathBuf; use std::path::PathBuf;
use std::slice::Iter; use std::slice::Iter;
// TODO - write some impl stuff in here so the main GUI stuff pub struct CommandManager {
// is a little bit more decoupled from this. I think a Display pub launcher: Option<PathBuf>,
// and perhaps the path to string conversion too could be pub iwad: Option<PathBuf>,
// removed from the GUI part pub pwads: Vec<PathBuf>,
pub warp: String,
pub difficulty: Option<Difficulty>,
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<String>), 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)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WadInfo { pub struct WadInfo {
@@ -20,28 +122,29 @@ pub struct LauncherInfo {
pub name: String, pub name: String,
} }
//trait Manager {}
pub struct MultiManager<T> { pub struct MultiManager<T> {
pub selectable: Vec<T>, pub selectable: Vec<T>,
pub current_selected: Vec<usize>, pub current_selected: Vec<usize>,
} }
impl <T> MultiManager<T> { impl<T: Clone> MultiManager<T> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
selectable: Vec::new(), selectable: Vec::new(),
current_selected: Vec::new(), current_selected: Vec::new(),
} }
} }
pub fn add(&mut self, item: T) { pub fn add(&mut self, item: &T) {
self.selectable.push(item); self.selectable.push(item.clone());
} }
pub fn get(&self, index: usize) -> Option<(&T, bool)> { pub fn get(&self, index: usize) -> Option<(&T, bool)> {
if index > self.selectable.len() { if index > self.selectable.len() {
None None
} else { } 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> { pub fn get_current(&self) -> Vec<&T> {
@@ -52,7 +155,7 @@ impl <T> MultiManager<T> {
} }
current_selected_items current_selected_items
} }
pub fn add_current(&mut self, index: usize) { pub fn set_current(&mut self, index: usize) {
assert!(index < self.selectable.len()); assert!(index < self.selectable.len());
self.current_selected.push(index); self.current_selected.push(index);
} }
@@ -63,6 +166,21 @@ impl <T> MultiManager<T> {
pub fn remove_selectable(&mut self, index: usize) { pub fn remove_selectable(&mut self, index: usize) {
assert!(index < self.selectable.len()); assert!(index < self.selectable.len());
self.selectable.remove(index); self.selectable.remove(index);
let mut new_selected: Vec<usize> = 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( pub fn iter_selectable_with_pos_and_selected(
&mut self, &mut self,
@@ -79,21 +197,24 @@ pub struct SingleManager<T> {
pub current_selected: Option<usize>, pub current_selected: Option<usize>,
} }
impl <T> SingleManager<T> { impl<T: Clone> SingleManager<T> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
selectable: Vec::new(), selectable: Vec::new(),
current_selected: None, current_selected: None,
} }
} }
pub fn add(&mut self, item: T) { pub fn add(&mut self, item: &T) {
self.selectable.push(item); self.selectable.push(item.clone());
} }
pub fn get(&self, index: usize) -> Option<(&T, bool)> { pub fn get(&self, index: usize) -> Option<(&T, bool)> {
if index > self.selectable.len() { if index > self.selectable.len() {
None None
} else { } 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> { pub fn get_current(&self) -> Option<&T> {
@@ -112,6 +233,9 @@ impl <T> SingleManager<T> {
pub fn remove_selectable(&mut self, index: usize) { pub fn remove_selectable(&mut self, index: usize) {
assert!(index < self.selectable.len()); assert!(index < self.selectable.len());
self.selectable.remove(index); 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( pub fn iter_selectable_with_pos_and_selected(
&mut self, &mut self,

View File

@@ -1,7 +1,7 @@
use config::{default_save_filename, load_config, save_config, Config}; use config::{default_save_filename, load_config, save_config, Config};
use eframe::egui; use eframe::egui;
use eframe::egui::Color32; 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 native_dialog::{MessageDialog, MessageType};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
@@ -26,19 +26,15 @@ enum FileType {
Pwad, Pwad,
Launcher, Launcher,
} }
// Great name, I know
enum MyErrors {
NoLauncher,
NoIwad,
}
struct RustDoomLauncher { struct RustDoomLauncher {
launcher_manager: SingleManager<LauncherInfo>, launcher_manager: SingleManager<LauncherInfo>,
iwad_manager: SingleManager<WadInfo>, iwad_manager: SingleManager<WadInfo>,
pwad_manager: MultiManager<WadInfo>, pwad_manager: MultiManager<WadInfo>,
name: String, add_name: String,
config_filename: PathBuf, config_filename: PathBuf,
config_file_loaded: bool, config_file_loaded: bool,
command_manager: CommandManager,
warp_string: String, warp_string: String,
difficulty: Difficulty, difficulty: Difficulty,
fast_monsters: bool, fast_monsters: bool,
@@ -55,7 +51,8 @@ impl Default for RustDoomLauncher {
launcher_manager: SingleManager::new(), launcher_manager: SingleManager::new(),
iwad_manager: SingleManager::new(), iwad_manager: SingleManager::new(),
pwad_manager: MultiManager::new(), pwad_manager: MultiManager::new(),
name: "".to_string(), command_manager: CommandManager::new(),
add_name: "".to_string(),
config_filename: default_save_filename(), config_filename: default_save_filename(),
config_file_loaded: false, config_file_loaded: false,
warp_string: String::new(), warp_string: String::new(),
@@ -77,96 +74,53 @@ impl RustDoomLauncher {
let config = load_config(&self.config_filename).unwrap(); let config = load_config(&self.config_filename).unwrap();
if let Some(iwads) = config.iwads { if let Some(iwads) = config.iwads {
for iwad in iwads { for iwad in iwads {
self.iwad_manager.add(iwad); self.iwad_manager.add(&iwad);
} }
} }
if let Some(pwads) = config.pwads { if let Some(pwads) = config.pwads {
for pwad in pwads { for pwad in pwads {
self.pwad_manager.add(pwad.clone()); self.pwad_manager.add(&pwad.clone());
} }
} }
if let Some(launchers) = config.launchers { if let Some(launchers) = config.launchers {
for launcher in 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) -> () { fn launch_doom(&self) -> () {
if let Ok((launcher, command)) = self.get_launcher_and_command(true) { match self.command_manager.generate_command() {
// Note to self, don't hit launch button when running from emacs Err(e) => {
Command::new(launcher).args(command).spawn(); 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() { let launcher = match self.launcher_manager.get_current() {
Some(l) => l, Some(l) => l,
None => { None => {
return Err(MyErrors::NoLauncher); return Err(CommandErrors::NoLauncher);
} }
}; };
let iwad = match self.iwad_manager.get_current() { let iwad = match self.iwad_manager.get_current() {
Some(iwad) => iwad, Some(iwad) => iwad,
None => { None => {
return Err(MyErrors::NoIwad); return Err(CommandErrors::NoIwad);
} }
}; };
Ok((launcher, iwad)) Ok((launcher, iwad))
@@ -230,9 +184,13 @@ impl eframe::App for RustDoomLauncher {
// Completely unsure // Completely unsure
if let Some(rp) = remove_pos { if let Some(rp) = remove_pos {
self.launcher_manager.remove_selectable(rp); 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 { if let Some(ap) = add_pos {
self.launcher_manager.set_current(ap); self.launcher_manager.set_current(ap);
self.command_manager.add_launcher(self.launcher_manager.get_current().unwrap())
} }
}); });
ui.separator(); ui.separator();
@@ -257,9 +215,13 @@ impl eframe::App for RustDoomLauncher {
} }
if let Some(rp) = remove_pos { if let Some(rp) = remove_pos {
self.iwad_manager.remove_selectable(rp); 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 { if let Some(ap) = add_pos {
self.iwad_manager.set_current(ap); self.iwad_manager.set_current(ap);
self.command_manager.add_iwad(self.iwad_manager.get_current().unwrap());
} }
}); });
ui.separator(); ui.separator();
@@ -283,9 +245,11 @@ impl eframe::App for RustDoomLauncher {
} }
if let Some(rp) = remove_pos { if let Some(rp) = remove_pos {
self.pwad_manager.remove_selectable(rp); self.pwad_manager.remove_selectable(rp);
self.command_manager.add_pwads(&self.pwad_manager.get_current())
} }
if let Some(ap) = add_pos { 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.checkbox(&mut self.display_command, "");
}); });
ui.horizontal_wrapped(|ui| { 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 let Some(l) = self.launcher_manager.get_current() {
if ui.add(egui::SelectableLabel::new(false, &l.name)).clicked() { if ui.add(egui::SelectableLabel::new(false, &l.name)).clicked() {
self.launcher_manager.remove_current(); self.launcher_manager.remove_current();
self.command_manager.remove_launcher();
} }
} else { } else {
ui.label("Select a launcher plz"); ui.label("Select a launcher plz");
@@ -341,7 +303,8 @@ impl eframe::App for RustDoomLauncher {
.add(egui::SelectableLabel::new(false, &iwad.name)) .add(egui::SelectableLabel::new(false, &iwad.name))
.clicked() .clicked()
{ {
self.launcher_manager.remove_current(); self.iwad_manager.remove_current();
self.command_manager.remove_iwad();
} }
} else { } else {
ui.label("Select an iwad plz"); ui.label("Select an iwad plz");
@@ -360,14 +323,15 @@ impl eframe::App for RustDoomLauncher {
} }
if let Some(r) = remove { if let Some(r) = remove {
self.pwad_manager.remove_current(r); self.pwad_manager.remove_current(r);
self.command_manager.add_pwads(&self.pwad_manager.get_current())
} }
}); });
if self.display_command { if self.display_command {
ui.horizontal(|ui| { 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; let window_size = frame.info().window_info.size;
ui.add( 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| { .show(ctx, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
let name_label = ui.label("Name"); 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); .labelled_by(name_label.id);
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
@@ -427,25 +391,25 @@ impl eframe::App for RustDoomLauncher {
} }
}); });
if ui.button("Add!").clicked() { 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; return;
} }
match self.selected_file_type { match self.selected_file_type {
FileType::Iwad => { FileType::Iwad => {
self.iwad_manager.add(WadInfo { self.iwad_manager.add(&WadInfo {
name: self.name.clone(), name: self.add_name.clone(),
path: self.selected_file_path.clone(), path: self.selected_file_path.clone(),
}); });
} }
FileType::Pwad => { FileType::Pwad => {
self.pwad_manager.add(WadInfo { self.pwad_manager.add(&WadInfo {
name: self.name.clone(), name: self.add_name.clone(),
path: self.selected_file_path.clone(), path: self.selected_file_path.clone(),
}); });
} }
FileType::Launcher => { FileType::Launcher => {
self.launcher_manager.add(LauncherInfo { self.launcher_manager.add(&LauncherInfo {
name: self.name.clone(), name: self.add_name.clone(),
path: self.selected_file_path.clone(), path: self.selected_file_path.clone(),
}); });
} }