diff --git a/Cargo.lock b/Cargo.lock index 9a61155..43d6b88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -705,7 +705,7 @@ dependencies = [ [[package]] name = "hoi4_province_editor" -version = "0.1.0" +version = "0.1.1" dependencies = [ "better-panic", "ccl-fxhash", diff --git a/Cargo.toml b/Cargo.toml index 1df4a44..cc94307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hoi4_province_editor" -version = "0.1.0" +version = "0.1.1" authors = ["ScottyThePilot "] edition = "2018" description = "Map editor application for Hearts of Iron IV" diff --git a/src/app.rs b/src/app.rs index 92d0b61..60f9c9f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,5 @@ pub mod canvas; +mod command; mod console; pub mod format; pub mod map; @@ -127,6 +128,7 @@ impl App { (false, _, Up, Button::Keyboard(Key::LShift)) => self.mod_shift = false, (false, _, Up, Button::Keyboard(Key::LCtrl)) => self.mod_ctrl = false, (false, _, Up, Button::Keyboard(Key::LAlt)) => self.mod_alt = false, + (false, _, Dn, Button::Keyboard(Key::O)) if self.mod_ctrl => self.open_map(self.mod_alt), (false, Some(canvas), state, button) => match (state, button) { (Dn, Button::Mouse(MouseButton::Left)) => self.start_painting(), (Up, Button::Mouse(MouseButton::Left)) => self.stop_painting(), @@ -137,7 +139,6 @@ impl App { (Dn, Button::Keyboard(Key::Y)) if self.mod_ctrl => canvas.redo(), (Dn, Button::Keyboard(Key::S)) if self.mod_ctrl && self.mod_shift => self.save_map_as(self.mod_alt), (Dn, Button::Keyboard(Key::S)) if self.mod_ctrl => self.save_map(), - (Dn, Button::Keyboard(Key::O)) if self.mod_ctrl => self.open_map(self.mod_alt), (Dn, Button::Keyboard(Key::Space)) => canvas.cycle_brush(self.console.handle()), (Dn, Button::Keyboard(Key::C)) if self.mod_shift => canvas.calculate_coastal_provinces(), (Dn, Button::Keyboard(Key::R)) if self.mod_shift => canvas.calculate_recolor_map(), @@ -199,14 +200,16 @@ impl App { } pub fn execute_command(&mut self) { - if let Some(_command) = self.console.enter_command() { - // TODO: Make the console actually work + if let Some(line) = self.console.enter_command() { + let canvas = self.canvas.as_mut(); + let console = self.console.handle(); + command::line(line, console, canvas); }; } fn is_canvas_modified(&self) -> bool { if let Some(canvas) = &self.canvas { - canvas.modified() + canvas.modified } else { false } @@ -248,7 +251,7 @@ impl App { fn open_map(&mut self, archive: bool) { if let Some(canvas) = &mut self.canvas { - if canvas.modified() { + if canvas.modified { if msg_dialog_unsaved_changes() { self.save_map(); }; @@ -297,11 +300,12 @@ impl App { fn save_map_location(&mut self, location: L) -> bool where L: TryInto, L::Error: fmt::Display { - let canvas = self.canvas.as_ref().expect("no canvas loaded"); + let canvas = self.canvas.as_mut().expect("no canvas loaded"); if let Some(location) = location.try_into().report(self.console.handle()) { let success_message = format!("Saved map to {}", location); if let Some(()) = canvas.save(&location).report(self.console.handle()) { self.console.push_system(Ok(success_message)); + canvas.modified = false; true } else { false diff --git a/src/app/canvas.rs b/src/app/canvas.rs index 284316d..708e25e 100644 --- a/src/app/canvas.rs +++ b/src/app/canvas.rs @@ -31,7 +31,7 @@ pub struct Canvas { problems: Vec, unknown_terrains: Option>, location: Location, - modified: bool, + pub modified: bool, pub camera: Camera } @@ -46,10 +46,10 @@ impl Canvas { let unknown_terrains = bundle.search_unknown_terrains(); let camera = Camera::new(&texture); - if let Some(unknown_terrains) = &unknown_terrains { - let unknown_terrains = unknown_terrains.iter().map(|s| s.to_uppercase()).join(", "); - return Err(format!("Unknown terrains present, not found in config: {}", unknown_terrains).into()); - }; + //if let Some(unknown_terrains) = &unknown_terrains { + // let unknown_terrains = unknown_terrains.iter().map(|s| s.to_uppercase()).join(", "); + // return Err(format!("Unknown terrains present, not found in config: {}", unknown_terrains).into()); + //}; Ok(Canvas { bundle, @@ -69,18 +69,22 @@ impl Canvas { self.bundle.save(location) } - pub fn modified(&self) -> bool { - self.modified - } - pub fn location(&self) -> &Location { &self.location } + pub fn view_mode(&self) -> ViewMode { + self.view_mode + } + pub fn set_location(&mut self, location: Location) { self.location = location; } + pub fn config(&self) -> Rc { + Rc::clone(&self.bundle.config) + } + pub fn draw(&self, ctx: Context, glyph_cache: &mut FontGlyphCache, camera_info: bool, gl: &mut GlGraphics) { let transform = ctx.transform.append_transform(self.camera.display_matrix); graphics::image(&self.texture, transform, gl); @@ -152,6 +156,10 @@ impl Canvas { }; } + pub fn brush_mut(&mut self) -> &mut BrushSettings { + &mut self.brush + } + pub fn cycle_brush(&mut self, mut console: ConsoleHandle) { match self.view_mode { ViewMode::Color => { @@ -329,11 +337,11 @@ impl Canvas { #[derive(Debug, Clone)] pub struct BrushSettings { - color_brush: Option, - kind_brush: Option, - terrain_brush: Option, - continent_brush: Option, - radius: f64 + pub color_brush: Option, + pub kind_brush: Option, + pub terrain_brush: Option, + pub continent_brush: Option, + pub radius: f64 } impl Default for BrushSettings { diff --git a/src/app/command.rs b/src/app/command.rs new file mode 100644 index 0000000..0ee4c61 --- /dev/null +++ b/src/app/command.rs @@ -0,0 +1,87 @@ +use super::console::ConsoleHandle; +use super::canvas::{Canvas, ViewMode}; +use super::format::DefinitionKind; + +pub fn line(line: String, mut console: ConsoleHandle, canvas: Option<&mut Canvas>) { + let mut arguments = line.split_whitespace(); + match arguments.next() { + Some("help") => help(console), + Some("commands") => commands(console), + Some("controls") => controls(console), + Some("select") => match (arguments.next(), canvas) { + (Some(arg), Some(canvas)) => select(console, canvas, arg), + (None, _) => console.push_system(Err("Command 'select' takes 2 arguments, found 1")), + (_, None) => console.push_system(Err("No map loaded")) + }, + Some(arg) => console.push_system(Err(format!("Invalid command '{}'", arg))), + None => console.push_system(Err("No command")) + }; +} + +fn help(mut console: ConsoleHandle) { + console.push_system(Ok("Use the 'commands' command for a list of all commands")); + console.push_system(Ok("Use the 'controls' command for a list of all controls and hotkeys")); +} + +fn commands(mut console: ConsoleHandle) { + for &[name, description] in COMMANDS_LIST { + console.push_system(Ok(format!("{:<24}{:>32}", name, description))); + }; +} + +fn controls(mut console: ConsoleHandle) { + for &[name, description] in CONTROLS_LIST { + console.push_system(Ok(format!("{:<24}{:>32}", name, description))); + }; +} + +fn select(mut console: ConsoleHandle, canvas: &mut Canvas, arg: &str) { + match canvas.view_mode() { + ViewMode::Color => console.push_system(Err("Use SPACEBAR to select a new color instead")), + ViewMode::Kind => match arg.parse::() { + Ok(kind) => canvas.brush_mut().kind_brush = Some(kind.into()), + Err(_) => console.push_system(Err("Invalid type, expected one of 'land', 'sea', or 'lake'")) + }, + ViewMode::Terrain => match canvas.config().terrains.contains_key(arg) { + true => canvas.brush_mut().terrain_brush = Some(arg.to_owned()), + false => console.push_system(Err("Invalid terrain")) + }, + ViewMode::Continent => match arg.parse::() { + Ok(continent) => canvas.brush_mut().continent_brush = Some(continent), + Err(_) => console.push_system(Err("Invalid continent, expected integer")) + }, + ViewMode::Coastal => console.push_system(Err("Coastal map mode does not support painting")) + }; +} + +const COMMANDS_LIST: &[[&str; 2]] = &[ + ["help", "Basic help"], + ["commands", "Shows the commands list"], + ["controls", "Shows the controls list"], +]; + +const CONTROLS_LIST: &[[&str; 2]] = &[ + ["1", "Color map mode"], + ["2", "Terrain map mode"], + ["3", "Type map mode"], + ["4", "Continent map mode"], + ["5", "Coastal map mode"], + ["MOUSE1 / LMB", "Paint with selected brush"], + ["MOUSE2 / RMB", "Pan camera"], + ["MOUSE3 / MMB", "Pick brush from map"], + ["SCROLL", "Zoom map view"], + ["SHIFT + SCROLL", "Resize brush"], + ["CTRL + Z", "Undo"], + ["CTRL + Y", "Redo"], + ["CTRL + SHIFT + S", "Save-As"], + ["CTRL + SHIFT + ALT + S", "Save-As Archive"], + ["CTRL + S", "Save"], + ["CTRL + O", "Open"], + ["CTRL + ALT + O", "Open Archive"], + ["SPACEBAR", "Cycle brush options"], + ["SHIFT + C", "Re-calculate coastal provinces"], + ["SHIFT + R", "Re-color all provinces"], + ["SHIFT + P", "Display map errors/warnings"], + ["H", "Reset camera view"], + ["~", "Toggle console"] +]; diff --git a/src/app/console.rs b/src/app/console.rs index 49c91c8..eb0a790 100644 --- a/src/app/console.rs +++ b/src/app/console.rs @@ -41,7 +41,7 @@ impl Console { let command = active_console.take(); let entry = format!("> {}", command); self.push(entry); - Some(command) + Some(command.to_lowercase()) } else { None } @@ -109,7 +109,7 @@ impl Console { Ok(t) => (t.into(), ConsoleColor::System), Err(t) => (t.into(), ConsoleColor::SystemError) }; - + self.messages.push_back(ConsoleMessage::new(text, color, self.max_lifetime)); } @@ -149,7 +149,7 @@ impl ConsoleMessage { fn dead(&self, now: Instant) -> bool { now.duration_since(self.epoch) > self.max_lifetime } - + fn color(&self, now: Instant) -> Color { let mut color = self.color.get(); color[3] = self.alpha_lifetime(now); diff --git a/src/main.rs b/src/main.rs index 5e0ba3b..9979cfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,10 @@ use piston::window::{Size, WindowSettings}; use crate::app::App; +use std::path::PathBuf; +use std::env; +use std::io; + const WINDOW_WIDTH: u32 = 1280; const WINDOW_HEIGHT: u32 = 720; const SCREEN: Size = Size { @@ -41,6 +45,9 @@ pub const APPNAME: &str = concat!("HOI4 Province Map Editor v", env!("CARGO_PKG_ fn main() { better_panic::install(); + let root = root_dir().unwrap(); + env::set_current_dir(root).unwrap(); + let opengl = OpenGL::V3_2; let mut window: GlutinWindow = WindowSettings::new(APPNAME, SCREEN) .graphics_api(opengl).resizable(false).vsync(true) @@ -86,3 +93,17 @@ fn main() { }; }; } + +fn root_dir() -> io::Result { + if let Some(manifest_dir) = env::var_os("CARGO_MANIFEST_DIR") { + return Ok(PathBuf::from(manifest_dir)); + }; + + let mut current_exe = env::current_exe()?.canonicalize()?; + + if current_exe.pop() { + return Ok(current_exe); + }; + + Err(io::Error::new(io::ErrorKind::Other, "Failed to find an application root")) +}