//! Home of the user configuratable content of graf karto, like keybindings and (TODO) colours etc. use crate::client::input::{Binding, Button, Input, MouseButton, Scancode}; use ron::de::from_reader; use ron::ser::{to_string_pretty, PrettyConfig}; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::{self, Write}; use std::path::Path; /// All configuration parameters the user can set are contained in this struct. #[allow(missing_docs)] #[derive(Deserialize, Serialize)] pub struct Config { pub tool_activation_binds: ToolActivationBinds, pub tool_general_binds: ToolGeneralBinds, pub icon_binds: IconToolBinds, } #[allow(missing_docs)] #[derive(Deserialize, Serialize)] /// The keys used to activate the individual tools. These keystrokes will not be sent to the tools, /// but instead will be handled by the editor where the tools are registered. pub struct ToolActivationBinds { pub deletion: Binding, pub icon: Binding, pub polygon_room: Binding, pub rect_room: Binding, pub selection: Binding, pub wall: Binding, } #[derive(Deserialize, Serialize)] /// Keys that are useful to most tools. These are packaged so that not every tool has the same n keys /// and then some more. pub struct ToolGeneralBinds { /// Keybinding to, where applicable, place a single node (usually a vertex) for the tool in /// question. pub place_single: Binding, /// Finish up whatever one is doing with the current tool, without removing information. pub finish: Binding, /// Abort whatever one is doing with the current tool which means the last atomic action will not /// be pushed into the map items. pub abort: Binding, } #[derive(Clone, Serialize, Deserialize)] /// Key bindings that are individually interesting to the icon tool. pub struct IconToolBinds { /// Key to change to the next icon of the icon list. pub next: Binding, /// Key to change to the previous icon of the icon list. pub previous: Binding, /// Rotate the working icon clockwise by a certain amount (currently 45 degrees) pub rotate_clockwise: Binding, /// Rotate the working icon counterclockwise by a certain amount (currently 45 degrees) pub rotate_counterclockwise: Binding, } impl Config { /// Try to parse a configuration from the file located at path. /// /// # Errors /// If the file is not found or can not be read or parsed for a different reason, an IO-Error is /// returned. pub fn from_file>(path: P) -> io::Result { let file = File::open(&path)?; match from_reader(file) { Ok(data) => Ok(data), Err(err) => Err(io::Error::new(io::ErrorKind::InvalidData, err)), } } /// Try to write the configuration to the file at path. If the file exists, it will be overwritten. /// /// # Errors /// If the file can not be written, for example for lack of permissions, an IO-Error is returned. pub fn write_file>(&self, path: P) -> io::Result<()> { let mut file = File::create(&path)?; let pretty_conf = PrettyConfig::new() .with_depth_limit(4) .with_decimal_floats(true) .with_separate_tuple_members(true) .with_indentor("\t".to_owned()); let string = match to_string_pretty(&self, pretty_conf) { Ok(string) => string, Err(err) => { return Err(io::Error::new(io::ErrorKind::InvalidInput, err)); } }; file.write_all(&string.as_bytes()) } } /// Registers all bindings from the given configuration into the input handler. Should the /// configuration change at runtime, the global bindings of the input handler need to be cleared and /// this function must be called again. pub fn register_bindings(config: &Config, input: &mut Input) { if !input.add_global(config.tool_activation_binds.deletion.clone()) { warn!("Tried to add deletion binding twice."); } if !input.add_global(config.tool_activation_binds.icon.clone()) { warn!("Tried to add icon binding twice."); } if !input.add_global(config.tool_activation_binds.polygon_room.clone()) { warn!("Tried to add polygon room binding twice."); } if !input.add_global(config.tool_activation_binds.rect_room.clone()) { warn!("Tried to add rect room binding twice."); } if !input.add_global(config.tool_activation_binds.selection.clone()) { warn!("Tried to add selection binding twice."); } if !input.add_global(config.tool_activation_binds.wall.clone()) { warn!("Tried to add wall binding twice."); } if !input.add_global(config.tool_general_binds.place_single.clone()) { warn!("Tried to add place single binding twice."); } if !input.add_global(config.tool_general_binds.finish.clone()) { warn!("Tried to add finish binding twice."); } if !input.add_global(config.tool_general_binds.abort.clone()) { warn!("Tried to add abort binding twice."); } if !input.add_global(config.icon_binds.next.clone()) { warn!("Tried to add next binding twice."); } if !input.add_global(config.icon_binds.previous.clone()) { warn!("Tried to add previous binding twice."); } if !input.add_global(config.icon_binds.rotate_clockwise.clone()) { warn!("Tried to add rotate clockwise binding twice."); } if !input.add_global(config.icon_binds.rotate_counterclockwise.clone()) { warn!("Tried to add rotate counterclockwise binding twice."); } } impl Default for Config { fn default() -> Self { Config { tool_activation_binds: ToolActivationBinds { deletion: Button::Text('d').into(), icon: Button::Text('i').into(), polygon_room: Button::Text('p').into(), rect_room: Button::Text('r').into(), selection: Button::Text('s').into(), wall: Button::Text('w').into(), }, tool_general_binds: ToolGeneralBinds { place_single: Button::Mouse(MouseButton::Left).into(), finish: Button::Scancode(Scancode::Enter).into(), abort: Button::Mouse(MouseButton::Right).into(), }, icon_binds: IconToolBinds { next: Button::Text('j').into(), previous: Button::Text('k').into(), rotate_clockwise: Button::Text('+').into(), rotate_counterclockwise: Button::Text('-').into(), }, } } }