use crate::grid::snap_to_grid; 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, KeyboardKey, MouseButton}; use raylib::{RaylibHandle, RaylibThread}; use ron::de::from_reader; use serde::Deserialize; use std::fs::{self, File}; pub const ICON_DIR: &'static str = "assets/icons"; #[derive(Deserialize)] struct IconInfo { /// 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, } pub struct IconTool { // TODO: support svg /// 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, IconInfo)>, /// The currently active icon, defined by its position in the Vec. active_icon: usize, current_icon_pos: Option>, } impl IconTool { pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> 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: IconInfo = from_reader(ron).expect("Could not parse icon info from reader."); icon_data.push((texture, icon_info)); } Self { icon_data, active_icon: 0, current_icon_pos: None, } } } impl Tool for IconTool { fn active_update(&mut self, map: &mut MapData, rl: &RaylibHandle, transform: &Transform) { // Put the currently active icon to where it would be placed. self.current_icon_pos = Some(snap_to_grid( transform.point_px_to_m(rl.get_mouse_position().into()), 0.5, )); // Activate the next icon when pressing the icon tool key. if rl.is_key_pressed(KeyboardKey::KEY_I) { self.active_icon = (self.active_icon + 1) % self.icon_data.len(); } // Handle placing the icon on the map if rl.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) { map.icons_mut() .push((self.active_icon, self.current_icon_pos.unwrap())); } } fn draw(&self, map: &MapData, rld: &mut RaylibDrawHandle, transform: &Transform) { // Draw all icons that have been placed on the map. for (icon, pos) in map.icons() { let (texture, info) = &self.icon_data[*icon]; rld.draw_texture_ex( texture, transform.point_m_to_px(*pos - (info.anchor / info.pixels_per_m)), 0., 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 let Some(current_icon_pos) = self.current_icon_pos { let (texture, info) = &self.icon_data[self.active_icon]; rld.draw_texture_ex( texture, transform.point_m_to_px(current_icon_pos - (info.anchor / info.pixels_per_m)), 0., transform.pixels_per_m() / info.pixels_per_m, Color { r: 120, g: 200, b: 120, a: 255, }, ); } } }