use crate::button::Button; use crate::config::{IconToolKeybindings, ToolKeybindings}; use crate::grid::{snap_to_grid, SNAP_SIZE}; use crate::map_data::MapData; use crate::math::Vec2; use crate::tool::Tool; use crate::transform::Transform; use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; use raylib::core::texture::Texture2D; use raylib::ffi::Color; use raylib::{RaylibHandle, RaylibThread}; use ron::de::from_reader; use serde::{Deserialize, Serialize}; use std::fs::{self, File}; pub const ICON_DIR: &str = "assets/icons"; #[derive(Deserialize)] struct IconFileInfo { /// The position the icon should be anchored in pixels. This is the Vector it will be moved by /// relative to the mouse pointer (to the left and up). anchor: Vec2, /// The scale of the icon as expressed in image pixels per real meter. pixels_per_m: f32, } #[derive(Clone, Serialize, Deserialize)] pub struct IconInfo { /// The id of the icon is the icons position in the currently loaded icon_data vector. pub icon_id: usize, /// The position of the icon on the map, given by the vector in meters. pub position: Vec2, /// Rotation of the icon texture in degrees. pub rotation: f32, } pub struct IconTool { // TODO: support svg keybindings: IconToolKeybindings, /// The icon data, containing the image texture and the info for that image texture like the /// scale the image actually has. icon_data: Vec<(Texture2D, IconFileInfo)>, /// Saves whether the IconTool is the currently active tool or not. active: bool, /// The information of the icon that should be placed / is currently being placed, if it /// exists. current_icon: IconInfo, } impl Default for IconInfo { fn default() -> Self { Self { icon_id: 0, position: Vec2::new(0., 0.), rotation: 0., } } } impl IconTool { pub fn new( rl: &mut RaylibHandle, rlt: &RaylibThread, keybindings: IconToolKeybindings, ) -> Self { /* Read all available icons from the icon directory. SVGs do not need any special scale * file, but pixel-based file formats require a RON-file declaring what the scale of the * picture is right beside them. */ let mut image_files = Vec::new(); for entry in fs::read_dir(ICON_DIR).expect("Could not open icon directory") { let entry = entry.expect("Failed to read file from icon directory"); // Ignore the RON-files for now and put the image files into the vec if entry .path() .extension() .expect("Entry does not have a file extension") != "ron" { image_files.push(entry); } } // Read the RON-files where it is necessary. let mut icon_data = Vec::with_capacity(image_files.len()); for file in image_files { // TODO: Handle svg let texture = rl .load_texture( rlt, file.path() .to_str() .expect("Unable to convert path to string."), ) .expect("Could not read image file"); let mut file = file.path(); file.set_extension("ron"); let ron = File::open(file).expect("Could not read ron file for icon information."); let icon_info: IconFileInfo = from_reader(ron).expect("Could not parse icon info from reader."); icon_data.push((texture, icon_info)); } Self { keybindings, icon_data, active: false, current_icon: IconInfo::default(), } } } impl Tool for IconTool { fn activate(&mut self) { self.active = true; } fn deactivate(&mut self) { self.active = false; } fn active_update(&mut self, map: &mut MapData, rl: &RaylibHandle, transform: &Transform) { // Update the position of the icon that should be drawn to the current mouse position. let snapped_mouse_pos_m = snap_to_grid( transform.point_px_to_m(rl.get_mouse_position().into()), SNAP_SIZE, ); self.current_icon.position = snapped_mouse_pos_m; // Unwrap the current icon, since it is now definitely set, as we are in the active update. if self.keybindings.next.is_pressed(rl) { self.current_icon.icon_id = (self.current_icon.icon_id + 1) % self.icon_data.len(); } if self.keybindings.rotate_clockwise.is_pressed(rl) { self.current_icon.rotation += 45.; } // Handle placing the icon on the map if self.keybindings.place.is_pressed(rl) { map.icons_mut().push(self.current_icon.clone()); } } fn draw(&self, map: &MapData, rld: &mut RaylibDrawHandle, transform: &Transform) { // Draw all icons that have been placed on the map. for icon in map.icons() { let (texture, info) = &self.icon_data[icon.icon_id]; // Round the position to whole pixels to fix rotation problems. let mut position_px = transform.point_m_to_px(icon.position - (info.anchor / info.pixels_per_m)); position_px.x = position_px.x as i32 as f32; position_px.y = position_px.y as i32 as f32; rld.draw_texture_ex( texture, position_px, icon.rotation, transform.pixels_per_m() / info.pixels_per_m, Color { r: 255, g: 255, b: 255, a: 255, }, ); } // Draw the icon that would be placed if self.active { let (texture, info) = &self.icon_data[self.current_icon.icon_id]; // Round the position to whole pixels to fix rotation problems. let mut position_px = transform .point_m_to_px(self.current_icon.position - (info.anchor / info.pixels_per_m)); position_px.x = position_px.x as i32 as f32; position_px.y = position_px.y as i32 as f32; rld.draw_texture_ex( texture, position_px, self.current_icon.rotation, transform.pixels_per_m() / info.pixels_per_m, Color { r: 120, g: 200, b: 120, a: 255, }, ); } } fn activation_key(&self) -> Button { self.keybindings.activation_key() } }