//! # Graf Karto cartographer //! //! ### What is it exactly //! Graf Karto is a table top role playing game (TTRPG) map creation tool that is optimised for real //! time map interaction. //! //! ### Motivation //! While there are certainly many TTRPG map creation tools for single user and multi user available //! online and on the market, we felt that most of them lack features or are to unwieldy to seriously //! consider for real time dungeon drawing, say for instance when drawing a map for an old school //! revival style game. This is why Graf Karto is optimised for speed above pretty graphical features. //! The aim is for the user not to have to think much about how they are going to achieve what they are //! doing and how they are going to make it pretty, but should just be able to *do* it, to have time to //! worry about other things. This does not mean that all maps created should visually be equivalent to //! a steaming pile of hot garbage, but if the visuals should try to get in the way, usability and speed //! takes precedence. #![allow(dead_code)] #![warn(missing_docs)] #[macro_use] extern crate log; pub mod cli; pub mod colours; pub mod config; pub mod editor; pub mod grid; pub mod gui; pub mod input; pub mod map; pub mod math; pub mod server; pub mod snapping; pub mod stable_vec; pub mod svg; pub mod tool; pub mod transform; pub mod transformable; use cli::CLI; use config::Config; use editor::Editor; use float_cmp::F64Margin; use gui::{DimensionIndicator, ToolSidebar}; use input::Input; use raylib::prelude::*; use snapping::Snapper; use std::ffi::CString; use std::io; use transform::Transform; /// Location of the file containing the style used for the raylib user interface. pub const GUI_STYLE: &str = "assets/style/cyber.rgs"; /// Location of the graf karto configuration options file. pub const CONFIG_FILE: &str = "config.ron"; /// The acceptable error that is used throughout the project for two floats to be considered equal. /// If it is set too low, the editor might not work properly, if set too high, the granularity may /// become too low for certain purposes. pub const FLOAT_MARGIN: F64Margin = F64Margin { epsilon: 10000. * f64::EPSILON, ulps: 0, }; fn main() { pretty_env_logger::init(); let (mut rl, thread) = raylib::init().resizable().title("Hello there!").build(); rl.set_target_fps(120); rl.set_exit_key(None); // Load the configuration file, if available. let config = match Config::from_file(CONFIG_FILE) { Ok(config) => config, Err(err) => { /* Create a default config file if it doesn't exist, otherwise leave the incorrectly * formatted/corrupted or otherwise unreadable file alone. */ let config = Config::default(); if err.kind() == io::ErrorKind::NotFound { warn!("Could not find a configuration file. Creating default."); config .write_file(CONFIG_FILE) .expect("Could not write config file."); } else { error!( "Could not read configuration file: {}\nUsing defaults for this run.", err ); } config } }; // Load the preferred gui style rl.gui_load_style(Some( &CString::new(GUI_STYLE).expect("Could not create C string from style file name"), )); let mut input = Input::new(&rl); config::register_bindings(&config, &mut input); let mut editor = Editor::new(&mut rl, &thread, config); let mut dimension_indicator = DimensionIndicator::new(); let mut tool_sidebar = ToolSidebar::new(&mut rl, &thread, &mut input); let mut snapper = Snapper::default(); let mut cli = CLI::new(&mut input); let mut transform = Transform::new(); let mut last_mouse_pos = rl.get_mouse_position(); while !rl.window_should_close() { let screen_width = rl.get_screen_width(); let screen_height = rl.get_screen_height(); input.update(&mut rl); // Move the canvas together with the mouse if rl.is_mouse_button_down(MouseButton::MOUSE_MIDDLE_BUTTON) { transform.move_by_px(&(rl.get_mouse_position() - last_mouse_pos).into()); } // Update the last mouse position last_mouse_pos = rl.get_mouse_position(); let mouse_wheel_move = rl.get_mouse_wheel_move(); if mouse_wheel_move != 0 { // Zoom in for positive and zoom out for negative mouse wheel rotations. let scale_factor = if mouse_wheel_move > 0 { 1.2 } else { 1. / 1.2 }; transform.try_zoom( &rl.get_mouse_position().into(), mouse_wheel_move.abs() as f64 * scale_factor, ); } cli.update(&mut editor, &mut input); dimension_indicator.update(editor.map_mut(), &mut rl); snapper.update(&mut rl, cli.active()); editor.update(&mut rl, &transform, &snapper, &mut input); tool_sidebar.update(screen_height as u16, &mut input); // Drawing section { let mut d = rl.begin_drawing(&thread); d.clear_background(Color::BLACK); grid::draw_grid(&mut d, screen_width, screen_height, &transform); editor.map().draw(&mut d, &transform); editor.draw_tools(&mut d, &transform); tool_sidebar.draw(&mut d, &mut editor); snapper.draw(&mut d); gui::position_indicator_draw(&mut d, last_mouse_pos.into(), &transform, &snapper); dimension_indicator.draw(&mut d, &transform); cli.draw(&mut d); } } }