diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.rs | 123 | ||||
| -rw-r--r-- | src/editor.rs | 135 | ||||
| -rw-r--r-- | src/gui/dimension_indicator.rs | 128 | ||||
| -rw-r--r-- | src/main.rs | 13 | ||||
| -rw-r--r-- | src/map/data.rs | 64 | ||||
| -rw-r--r-- | src/map/icon.rs | 82 | ||||
| -rw-r--r-- | src/map/icon_renderer.rs | 77 | ||||
| -rw-r--r-- | src/map/mappable.rs | 20 | ||||
| -rw-r--r-- | src/map/mod.rs | 122 | ||||
| -rw-r--r-- | src/map/polygon_room.rs | 68 | ||||
| -rw-r--r-- | src/map/rect_room.rs | 68 | ||||
| -rw-r--r-- | src/map/wall.rs | 98 | ||||
| -rw-r--r-- | src/map_data.rs | 97 | ||||
| -rw-r--r-- | src/math/line_segment.rs | 3 | ||||
| -rw-r--r-- | src/math/mod.rs | 18 | ||||
| -rw-r--r-- | src/math/polygon/mod.rs | 4 | ||||
| -rw-r--r-- | src/math/rect.rs | 36 | ||||
| -rw-r--r-- | src/math/surface.rs | 21 | ||||
| -rw-r--r-- | src/scaleable.rs | 11 | ||||
| -rw-r--r-- | src/tool/deletion_tool.rs | 75 | ||||
| -rw-r--r-- | src/tool/icon_tool.rs | 197 | ||||
| -rw-r--r-- | src/tool/mod.rs | 46 | ||||
| -rw-r--r-- | src/tool/polygon_room_tool.rs | 137 | ||||
| -rw-r--r-- | src/tool/rect_room_tool.rs | 78 | ||||
| -rw-r--r-- | src/tool/room_tool.rs | 103 | ||||
| -rw-r--r-- | src/tool/wall_tool.rs | 113 |
26 files changed, 1168 insertions, 769 deletions
diff --git a/src/config.rs b/src/config.rs index b4703a2..4e63387 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,58 +8,33 @@ use std::path::Path; #[derive(Deserialize, Serialize)] pub struct Config { - pub deletion_keybindings: DeletionToolKeybindings, - pub icon_keybindings: IconToolKeybindings, - pub polygon_keybindings: PolygonRoomToolKeybindings, - pub room_keybindings: RoomToolKeybindings, - pub wall_keybindings: WallToolKeybindings, + pub tool_activation_keys: ToolActivationKeys, + pub tool_general_keys: ToolGeneralKeys, + pub icon_keys: IconToolKeys, } -pub trait ToolKeybindings { - /// Get the key which will activate the tool it is made for. - fn activation_key(&self) -> Button; +#[derive(Deserialize, Serialize)] +pub struct ToolActivationKeys { + pub deletion: Button, + pub icon: Button, + pub polygon_room: Button, + pub rect_room: Button, + pub wall: Button, } -#[derive(Serialize, Deserialize)] -pub struct DeletionToolKeybindings { - pub activation: Button, - pub start_selection: Button, - pub do_delete: Button, - pub abort_deletion: Button, +#[derive(Deserialize, Serialize)] +pub struct ToolGeneralKeys { + pub place_single: Button, + pub finish: Button, + pub abort: Button, } -#[derive(Serialize, Deserialize)] -pub struct IconToolKeybindings { - pub activation: Button, +#[derive(Clone, Serialize, Deserialize)] +pub struct IconToolKeys { pub next: Button, pub previous: Button, pub rotate_clockwise: Button, pub rotate_counterclockwise: Button, - pub place: Button, -} - -#[derive(Serialize, Deserialize)] -pub struct PolygonRoomToolKeybindings { - pub activation: Button, - pub place_node: Button, - pub finish: Button, - pub abort: Button, -} - -#[derive(Serialize, Deserialize)] -pub struct RoomToolKeybindings { - pub activation: Button, - pub start_draw: Button, - pub finish_draw: Button, - pub abort_draw: Button, -} - -#[derive(Serialize, Deserialize)] -pub struct WallToolKeybindings { - pub activation: Button, - pub start_wall: Button, - pub finish_segment: Button, - pub abort_segment: Button, } impl Config { @@ -93,64 +68,24 @@ impl Config { impl Default for Config { fn default() -> Self { Config { - deletion_keybindings: DeletionToolKeybindings { - activation: Button::Keyboard(KeyboardKey::D), - start_selection: Button::Mouse(MouseButton::Left), - do_delete: Button::Mouse(MouseButton::Left), - abort_deletion: Button::Mouse(MouseButton::Right), + tool_activation_keys: ToolActivationKeys { + deletion: Button::Keyboard(KeyboardKey::D), + icon: Button::Keyboard(KeyboardKey::I), + polygon_room: Button::Keyboard(KeyboardKey::P), + rect_room: Button::Keyboard(KeyboardKey::R), + wall: Button::Keyboard(KeyboardKey::W), }, - icon_keybindings: IconToolKeybindings { - activation: Button::Keyboard(KeyboardKey::I), + tool_general_keys: ToolGeneralKeys { + place_single: Button::Mouse(MouseButton::Left), + finish: Button::Keyboard(KeyboardKey::Enter), + abort: Button::Mouse(MouseButton::Right), + }, + icon_keys: IconToolKeys { next: Button::Keyboard(KeyboardKey::I), previous: Button::Keyboard(KeyboardKey::J), rotate_clockwise: Button::Mouse(MouseButton::Right), rotate_counterclockwise: Button::Keyboard(KeyboardKey::Minus), - place: Button::Mouse(MouseButton::Left), - }, - polygon_keybindings: PolygonRoomToolKeybindings { - activation: Button::Keyboard(KeyboardKey::P), - place_node: Button::Mouse(MouseButton::Left), - finish: Button::Keyboard(KeyboardKey::Enter), - abort: Button::Mouse(MouseButton::Right), - }, - room_keybindings: RoomToolKeybindings { - activation: Button::Keyboard(KeyboardKey::R), - start_draw: Button::Mouse(MouseButton::Left), - finish_draw: Button::Mouse(MouseButton::Left), - abort_draw: Button::Mouse(MouseButton::Right), - }, - wall_keybindings: WallToolKeybindings { - activation: Button::Keyboard(KeyboardKey::W), - start_wall: Button::Mouse(MouseButton::Left), - finish_segment: Button::Mouse(MouseButton::Left), - abort_segment: Button::Mouse(MouseButton::Right), }, } } } - -impl ToolKeybindings for DeletionToolKeybindings { - fn activation_key(&self) -> Button { - self.activation - } -} -impl ToolKeybindings for IconToolKeybindings { - fn activation_key(&self) -> Button { - self.activation - } -} -impl ToolKeybindings for PolygonRoomToolKeybindings { - fn activation_key(&self) -> Button { - self.activation - } -} -impl ToolKeybindings for RoomToolKeybindings { - fn activation_key(&self) -> Button { - self.activation - } -} -impl ToolKeybindings for WallToolKeybindings { - fn activation_key(&self) -> Button { - self.activation - } -} diff --git a/src/editor.rs b/src/editor.rs index b2260f1..bbe4f60 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,71 +1,108 @@ +use crate::button::{Button, MouseButton}; use crate::config::Config; -use crate::map_data::MapData; +use crate::map::Map; use crate::tool::*; use crate::transform::Transform; use raylib::core::drawing::RaylibDrawHandle; -use raylib::ffi::KeyboardKey; use raylib::{RaylibHandle, RaylibThread}; -use std::mem; +use std::collections::HashMap; +use crate::grid::{snap_to_grid, SNAP_SIZE}; pub struct Editor { - map_data: MapData, - tools: Vec<Box<dyn Tool>>, - active: usize, + map: Map, + /// HashMap that matches the ToolType with its proper activation key and of course the tool + /// itself. + tools: HashMap<ToolType, (Box<dyn Tool>, Button)>, + active: ToolType, + config: Config } impl Editor { pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread, config: Config) -> Self { - let mut tools: Vec<Box<dyn Tool>> = Vec::with_capacity(ToolType::NumTools as usize); - assert_eq!(ToolType::RoomTool as u8, 0); - tools.push(Box::new(RoomTool::new(config.room_keybindings))); - assert_eq!(ToolType::PolygonRoomTool as u8, 1); - tools.push(Box::new(PolygonRoomTool::new(config.polygon_keybindings))); - assert_eq!(ToolType::WallTool as u8, 2); - tools.push(Box::new(WallTool::new(config.wall_keybindings))); - assert_eq!(ToolType::IconTool as u8, 3); - tools.push(Box::new(IconTool::new(rl, rlt, config.icon_keybindings))); - assert_eq!(ToolType::DeletionTool as u8, 4); - tools.push(Box::new(DeletionTool::new(config.deletion_keybindings))); + let map = Map::new(rl, rlt); + + let mut tools: HashMap<ToolType, (Box<dyn Tool>, Button)> = + HashMap::with_capacity(ToolType::NumTools as usize); + + tools.insert( + ToolType::RectRoomTool, + ( + Box::new(RectRoomTool::new()), + config.tool_activation_keys.rect_room, + ), + ); + tools.insert( + ToolType::PolygonRoomTool, + ( + Box::new(PolygonRoomTool::new()), + config.tool_activation_keys.polygon_room, + ), + ); + tools.insert( + ToolType::WallTool, + (Box::new(WallTool::new()), config.tool_activation_keys.wall), + ); + tools.insert( + ToolType::IconTool, + ( + Box::new(IconTool::new(config.icon_keys.clone(), map.icon_renderer())), + config.tool_activation_keys.icon, + ), + ); + tools.insert( + ToolType::DeletionTool, + ( + Box::new(DeletionTool::new()), + config.tool_activation_keys.deletion, + ), + ); assert_eq!(ToolType::NumTools as usize, tools.len()); Self { - map_data: MapData::new(), + map, tools, - active: 0, + active: ToolType::RectRoomTool, + config } } /// Get the currently active tool. pub fn active(&self) -> ToolType { - unsafe { mem::transmute(self.active as u8) } + self.active } /// Set the currently active tool. Any process currently going on in a different tool will be /// aborted. pub fn set_active(&mut self, tool: ToolType) { - if tool as usize != self.active { - self.tools[self.active].deactivate(); - self.active = tool as usize; - self.tools[self.active].activate(); + if tool != self.active { + self.tools.get_mut(&self.active).unwrap().0.deactivate(); + self.active = tool; + self.tools + .get_mut(&self.active) + .expect("{:?} is not a Tool in the Editor. Maybe you forgot to register it?") + .0 + .activate(); } } - pub fn update(&mut self, rl: &RaylibHandle, transform: &Transform, mouse_blocked: bool) { + pub fn update(&mut self, rl: &mut RaylibHandle, transform: &Transform, mouse_blocked: bool) { // Handle keybindings for tool change - for (i, tool) in self.tools.iter().enumerate() { - if tool.activation_key().is_pressed(rl, false) { + for (&tool_type, (_, activation_key)) in self.tools.iter() { + if activation_key.is_pressed(rl, false) { // Don't do anything if the tool does not change. - if i == self.active { + if tool_type == self.active { break; } // Activate the tool of which the key binding has been pressed. - self.set_active(unsafe { mem::transmute(i as u8) }); + self.set_active(tool_type); break; } } + /* + TODO: reintroduce saving and loading // Handle saving and loading the editor contents to the swap file if rl.is_key_pressed(KeyboardKey::KEY_S) { self.map_data @@ -76,21 +113,47 @@ impl Editor { .load_file("swap.ron") .expect("Unable to read buffer file"); } + */ + + let mouse_pos_m = transform.point_px_to_m(&rl.get_mouse_position().into()); + let snapped_mouse_pos = snap_to_grid(mouse_pos_m, SNAP_SIZE); - for tool in &mut self.tools { - tool.update(&self.map_data, rl, transform); + // Update the currently active tool + let active_tool = &mut self.tools.get_mut(&self.active).unwrap().0; + active_tool.update(&self.map, &snapped_mouse_pos); + + // Handle common keybindings many of the tools have. + if self.config.tool_general_keys.place_single.is_pressed(rl, mouse_blocked) { + active_tool.place_single(&mut self.map, &snapped_mouse_pos); + } + if self.config.tool_general_keys.finish.is_pressed(rl, mouse_blocked) { + active_tool.finish(&mut self.map); } + if self.config.tool_general_keys.abort.is_pressed(rl, mouse_blocked) { + active_tool.abort(); + } + + // Handle custom keybindings in case the tool has any. + let latest_button = if let Some(keyboard_key) = rl.get_key_pressed() { + Some(Button::Keyboard(keyboard_key.into())) + } + else { + let mouse_buttons = [Button::Mouse(MouseButton::Left), Button::Mouse(MouseButton::Middle), Button::Mouse(MouseButton::Right)]; + mouse_buttons.iter().find(|button| button.is_pressed(rl, mouse_blocked)).copied() + }; - self.tools[self.active].active_update(&mut self.map_data, rl, transform, mouse_blocked); + if let Some(latest_button) = latest_button { + active_tool.on_button_pressed(&mut self.map, latest_button); + } } pub fn draw_tools(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { - for tool in &self.tools { - tool.draw(&self.map_data, rld, transform); + for (tool, _) in self.tools.values() { + tool.draw(rld, transform); } } - pub fn map_data(&self) -> &MapData { - &self.map_data + pub fn map(&self) -> &Map { + &self.map } } diff --git a/src/gui/dimension_indicator.rs b/src/gui/dimension_indicator.rs new file mode 100644 index 0000000..cc12f0b --- /dev/null +++ b/src/gui/dimension_indicator.rs @@ -0,0 +1,128 @@ +use crate::math::Vec2; +use crate::transform::Transform; +use raylib::drawing::RaylibDraw; +use raylib::ffi::Color; + +pub struct DimensionIndicator { + /// The lines that are used to draw the Dimension Indicator. For a rectangle for instance these + /// would be two. One for width and one for height. + length_lines: Vec<(Vec2<f64>, Vec2<f64>)>, +} + +impl DimensionIndicator { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + length_lines: Vec::new(), + } + } + + pub fn from_corner_points(corner_points: &[Vec2<f64>]) -> Self { + let mut this = Self::new(); + this.update_dimensions(corner_points); + + this + } + + pub fn clear_dimensions(&mut self) { + self.length_lines.clear(); + } + + /// Update the dimensions by analysing a given set of points and adjusting the internal + /// (measured) dimensions. + pub fn update_dimensions(&mut self, corner_points: &[Vec2<f64>]) { + if corner_points.len() < 2 { + warn!("Cannot discern dimensions when not at least two points are given. The dimensions were not updated."); + return; + } + + // Discern the bounding box for the given corner points. + let mut min = corner_points[0]; + let mut max = corner_points[0]; + for point in corner_points.iter().skip(1) { + if point.x < min.x { + min.x = point.x; + } + if point.x > max.x { + max.x = point.x; + } + + if point.y < min.y { + min.y = point.y; + } + if point.y > max.y { + max.y = point.y; + } + } + + // For now, only use the width and height vectors. + // TODO: Change to a more sophisticated approach. + self.length_lines.clear(); + // Horizontal dimensions, left to right. + self.length_lines + .push((Vec2::new(min.x, max.y), Vec2::new(max.x, max.y))); + // Vertical dimensions, bottom to top. + self.length_lines + .push((Vec2::new(max.x, max.y), Vec2::new(max.x, min.y))); + } + + pub fn draw(&self, rld: &mut impl RaylibDraw, transform: &Transform) { + // Draw all the dimension lines. + for (start, end) in &self.length_lines { + // Don't draw anything if the length is zero. + if start == end { + continue; + } + + /* Get the vector that is perpendicular and points right/down from the line, assuming + * the lines prefer left as start over right and bottom over top. + */ + let line_normal = { + // Start with the direction of the line vector. + let dir = *start - *end; + // Calculate perpendicular vec and normalise. + dir.rotated_90_clockwise() / dir.length() + }; + + // To not have the line directly in the rect, move start and end outside a bit. + let start_px = transform.point_m_to_px(start) + line_normal * 10.; + let end_px = transform.point_m_to_px(end) + line_normal * 10.; + + /* Draw the indicator line, with stubs at both ends. */ + let line_colour = Color { + r: 200, + g: 200, + b: 200, + a: 255, + }; + // First the two stubs. + rld.draw_line_ex( + start_px - line_normal * 5., + start_px + line_normal * 5., + 2., + line_colour, + ); + rld.draw_line_ex( + end_px - line_normal * 5., + end_px + line_normal * 5., + 2., + line_colour, + ); + // Then the actual indicator line. + rld.draw_line_ex(start_px, end_px, 2., line_colour); + + /* Draw the indicator text showing how long this line is in meters. + * It should be placed in the middle of the line, but not into the line directly, so it + * will be moved out by the normal. + */ + let text_pos = transform.point_m_to_px(&((*end + *start) / 2.)) + line_normal * 20.; + rld.draw_text( + &format!("{}m", &(*end - *start).length()), + text_pos.x as i32, + text_pos.y as i32, + 20, + line_colour, + ); + } + } +} diff --git a/src/main.rs b/src/main.rs index 7e04456..de42ab7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,9 @@ pub mod dimension_indicator; pub mod editor; pub mod grid; pub mod gui; -pub mod map_data; +pub mod map; pub mod math; +pub mod scaleable; pub mod svg; pub mod tool; pub mod transform; @@ -75,6 +76,8 @@ fn main() { 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 { @@ -87,19 +90,17 @@ fn main() { } editor.update( - &rl, + &mut rl, &transform, - ToolSidebar::mouse_captured(screen_height as u16, rl.get_mouse_position().into()), + ToolSidebar::mouse_captured(screen_height as u16, last_mouse_pos.into()), ); - // Update the last mouse position - last_mouse_pos = rl.get_mouse_position(); - // 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(screen_height as u16, &mut d, &mut editor); diff --git a/src/map/data.rs b/src/map/data.rs new file mode 100644 index 0000000..f978081 --- /dev/null +++ b/src/map/data.rs @@ -0,0 +1,64 @@ +use super::{IconData, PolygonRoomData, RectRoomData, WallData}; +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; + +/// The serialisable and deserialisable parts of the map. This can be created to get a version of the +/// map which is persistifiable or sendable/receivable without data overhead or data that might make +/// it easily corruptable. +#[derive(Serialize, Deserialize)] +pub struct MapData { + pub(super) rect_rooms: Vec<RectRoomData>, + pub(super) polygon_rooms: Vec<PolygonRoomData>, + pub(super) walls: Vec<WallData>, + pub(super) icons: Vec<IconData>, +} + +impl MapData { + pub fn new( + rect_rooms: Vec<RectRoomData>, + polygon_rooms: Vec<PolygonRoomData>, + walls: Vec<WallData>, + icons: Vec<IconData>, + ) -> Self { + Self { + rect_rooms, + polygon_rooms, + walls, + icons, + } + } + + pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Self> { + let file = File::open(&path)?; + let data: Self = match from_reader(file) { + Ok(data) => data, + Err(err) => { + return Err(io::Error::new(io::ErrorKind::InvalidData, err)); + } + }; + + Ok(data) + } + + pub fn write_to_file<P: AsRef<Path>>(&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()) + } +} diff --git a/src/map/icon.rs b/src/map/icon.rs new file mode 100644 index 0000000..50e1906 --- /dev/null +++ b/src/map/icon.rs @@ -0,0 +1,82 @@ +use super::icon_renderer::IconRenderer; +use crate::map::Mappable; +use crate::math::{Vec2, Rect}; +use crate::transform::Transform; +use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; +use serde::{Deserialize, Serialize}; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +#[derive(Clone, Serialize, Deserialize)] +pub struct IconData { + /// The id of the icon is the icons position in the currently loaded icon_data vector. + pub id: usize, + /// The position of the icon on the map, given by the vector in meters. + pub position: Vec2<f64>, + /// Rotation of the icon texture in degrees. + pub rotation: f64, +} + +#[derive(Clone)] +pub struct Icon { + data: IconData, + renderer: Rc<IconRenderer>, +} + +impl Icon { + pub fn new(id: usize, position: Vec2<f64>, rotation: f64, renderer: Rc<IconRenderer>) -> Self { + Self::from_data( + IconData { + id, + position, + rotation, + }, + renderer, + ) + } + + pub fn from_data(data: IconData, renderer: Rc<IconRenderer>) -> Self { + Self { data, renderer } + } +} + +impl Mappable for Icon { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + let (texture, info) = self.renderer.get(self.id); + // Round the position to whole pixels to fix rotation problems. + let mut position_px = + transform.point_m_to_px(&(self.position - (info.anchor / info.pixels_per_m))); + position_px.x = position_px.x.floor(); + position_px.y = position_px.y.floor(); + rld.draw_texture_ex( + texture, + position_px, + self.rotation as f32, + (transform.pixels_per_m() / info.pixels_per_m) as f32, + Color { + r: 255, + g: 255, + b: 255, + a: 255, + }, + ); + } + + fn bounding_rect(&self) -> Rect<f64> { + Rect::new(self.data.position.x, self.data.position.y, 0., 0.) + } +} + +impl Deref for Icon { + type Target = IconData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} +impl DerefMut for Icon { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/src/map/icon_renderer.rs b/src/map/icon_renderer.rs new file mode 100644 index 0000000..fb81e24 --- /dev/null +++ b/src/map/icon_renderer.rs @@ -0,0 +1,77 @@ +use crate::math::Vec2; +use raylib::core::texture::Texture2D; +use raylib::{RaylibHandle, RaylibThread}; +use ron::de::from_reader; +use serde::Deserialize; +use std::fs::{self, File}; + +pub const ICON_DIR: &str = "assets/icons"; + +#[derive(Deserialize)] +pub(super) 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). + pub anchor: Vec2<f64>, + /// The scale of the icon as expressed in image pixels per real meter. + pub pixels_per_m: f64, +} + +pub struct IconRenderer { + texture_data: Vec<(Texture2D, IconFileInfo)>, +} + +impl IconRenderer { + 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 texture_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."); + + texture_data.push((texture, icon_info)); + } + + Self { texture_data } + } + + pub(super) fn get(&self, icon_id: usize) -> &(Texture2D, IconFileInfo) { + &self.texture_data[icon_id] + } + + pub fn num_icons(&self) -> usize { + self.texture_data.len() + } +} diff --git a/src/map/mappable.rs b/src/map/mappable.rs new file mode 100644 index 0000000..f75c990 --- /dev/null +++ b/src/map/mappable.rs @@ -0,0 +1,20 @@ +//! Something that's mappable can be placed on the map and drawn at a specific position. It has a +//! dimension on the map and may be scaleable + +use crate::math::Rect; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::RaylibDrawHandle; + +pub trait Mappable { + /// Draw this to `rld` with the transform. An item that cannot be drawn is not mappable, so + /// this must always be implemented. + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform); + + /// Get the rectangle that contains the mappable object in its entirety without excess. + fn bounding_rect(&self) -> Rect<f64>; + + fn as_scaleable(&self) -> Option<&dyn Scaleable> { + None + } +} diff --git a/src/map/mod.rs b/src/map/mod.rs new file mode 100644 index 0000000..93f51a2 --- /dev/null +++ b/src/map/mod.rs @@ -0,0 +1,122 @@ +pub mod data; +pub mod icon; +pub mod icon_renderer; +pub mod mappable; +pub mod polygon_room; +pub mod rect_room; +pub mod wall; + +pub use data::MapData; +pub use icon::*; +pub use mappable::Mappable; +pub use polygon_room::*; +pub use rect_room::*; +pub use wall::*; + +use crate::transform::Transform; +use icon_renderer::IconRenderer; +use raylib::drawing::RaylibDrawHandle; +use raylib::{RaylibHandle, RaylibThread}; +use std::rc::Rc; + +pub struct Map { + rect_rooms: Vec<RectRoom>, + polygon_rooms: Vec<PolygonRoom>, + walls: Vec<Wall>, + icons: Vec<Icon>, + icon_renderer: Rc<IconRenderer>, +} + +impl Map { + pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self { + Self { + rect_rooms: Vec::new(), + polygon_rooms: Vec::new(), + walls: Vec::new(), + icons: Vec::new(), + icon_renderer: Rc::new(IconRenderer::new(rl, rlt)), + } + } + + pub fn push_rect_room(&mut self, room_data: RectRoomData) { + self.rect_rooms.push(RectRoom::from_data(room_data)); + } + + pub fn push_polygon_room(&mut self, room_data: PolygonRoomData) { + self.polygon_rooms.push(PolygonRoom::from_data(room_data)); + } + + pub fn push_wall(&mut self, wall_data: WallData) { + /* Check for intersections with any wall that was arleady created so the wall ends can be + * rendered properly. + */ + let mut start_intersects = false; + let mut end_intersects = false; + for wall in &self.walls { + if wall.data().contains_collinear(wall_data.start) { + start_intersects = true; + } + if wall.data().contains_collinear(wall_data.end) { + end_intersects = true; + } + + // Currently, additional intersections can be ignored, since it is just a yes-no-question + if start_intersects && end_intersects { + break; + } + } + + self.walls + .push(Wall::from_data(wall_data, start_intersects, end_intersects)); + } + + pub fn push_icon(&mut self, icon: Icon) { + self.icons.push(icon); + } + + pub fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + for element in self.iter() { + element.draw(rld, transform); + } + } + + pub fn icon_renderer(&self) -> Rc<IconRenderer> { + self.icon_renderer.clone() + } + + /// Retain all map elements that fulfill the given predicate. + pub fn retain<F>(&mut self, mut f: F) + where + F: FnMut(&dyn Mappable) -> bool, + { + // Call retain on all vectors containing the maps actual types. + self.rect_rooms.retain(|r| f(r as &dyn Mappable)); + self.polygon_rooms.retain(|p| f(p as &dyn Mappable)); + self.walls.retain(|w| f(w as &dyn Mappable)); + self.icons.retain(|i| f(i as &dyn Mappable)); + } + + /// Iterator over all elements as objects when an operation needs to go over all elements of the + /// map. + pub fn iter(&self) -> impl Iterator<Item = &dyn Mappable> { + self.rect_rooms + .iter() + .map(|r| r as &dyn Mappable) + .chain(self.polygon_rooms.iter().map(|p| p as &dyn Mappable)) + .chain(self.walls.iter().map(|w| w as &dyn Mappable)) + .chain(self.icons.iter().map(|i| i as &dyn Mappable)) + } + + pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut dyn Mappable> { + self.rect_rooms + .iter_mut() + .map(|r| r as &mut dyn Mappable) + .chain( + self.polygon_rooms + .iter_mut() + .map(|p| p as &mut dyn Mappable), + ) + .chain(self.walls.iter_mut().map(|w| w as &mut dyn Mappable)) + .chain(self.icons.iter_mut().map(|i| i as &mut dyn Mappable)) + } +} diff --git a/src/map/polygon_room.rs b/src/map/polygon_room.rs new file mode 100644 index 0000000..12c480b --- /dev/null +++ b/src/map/polygon_room.rs @@ -0,0 +1,68 @@ +use super::Mappable; +use crate::math::{self, Rect, Polygon, Triangle, Vec2}; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; + +pub type PolygonRoomData = Polygon<f64>; + +pub struct PolygonRoom { + data: PolygonRoomData, + // The polygon shape, but in triangles, so the polygon does not have to be triangulated every frame. + triangulated: Vec<Triangle<f64>>, +} + +impl PolygonRoom { + pub fn from_data(data: PolygonRoomData) -> Self { + Self { + data: data.clone(), + triangulated: math::triangulate(data) + } + } + + fn retriangulate(&mut self) { + self.triangulated = math::triangulate(self.data.clone()); + } +} + +impl Mappable for PolygonRoom { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + for triangle in &self.triangulated { + rld.draw_triangle( + transform.point_m_to_px(&triangle.corners()[0]), + transform.point_m_to_px(&triangle.corners()[1]), + transform.point_m_to_px(&triangle.corners()[2]), + Color { + r: 180, + g: 180, + b: 180, + a: 255, + }, + ) + } + } + + fn bounding_rect(&self) -> Rect<f64> { + Rect::bounding_rect_n(&self.data.corners()) + } + + fn as_scaleable(&self) -> Option<&dyn Scaleable> { + Some(self as &dyn Scaleable) + } +} + +impl Scaleable for PolygonRoom { + fn scale(&mut self, by: &Vec2<f64>) { + if by.x < 0. || by.y < 0. { + panic!("Cannot set dimensions with negative size"); + } + + for corner in self.data.corners_mut() { + corner.x *= by.x; + corner.y *= by.y; + } + + self.retriangulate(); + } +} diff --git a/src/map/rect_room.rs b/src/map/rect_room.rs new file mode 100644 index 0000000..07f201c --- /dev/null +++ b/src/map/rect_room.rs @@ -0,0 +1,68 @@ +use crate::map::Mappable; +use crate::math::{Rect, Vec2}; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; +use serde::Serialize; +use std::ops::{Deref, DerefMut}; + +pub type RectRoomData = Rect<f64>; + +#[derive(Serialize)] +pub struct RectRoom { + data: RectRoomData, +} + +impl RectRoom { + pub fn from_data(data: RectRoomData) -> Self { + RectRoom { data } + } +} + +impl Mappable for RectRoom { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + rld.draw_rectangle_rec( + transform.rect_m_to_px(&self.data), + Color { + r: 180, + g: 180, + b: 180, + a: 255, + }, + ); + } + + fn bounding_rect(&self) -> Rect<f64> { + self.data.clone() + } + + fn as_scaleable(&self) -> Option<&dyn Scaleable> { + Some(self as &dyn Scaleable) + } +} + +impl Scaleable for RectRoom { + fn scale(&mut self, by: &Vec2<f64>) { + if by.x < 0. || by.y < 0. { + panic!("Cannot set dimensions with negative size"); + } + + self.data.x *= by.x; + self.data.y *= by.y; + } +} + +impl Deref for RectRoom { + type Target = RectRoomData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for RectRoom { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/src/map/wall.rs b/src/map/wall.rs new file mode 100644 index 0000000..6c90fda --- /dev/null +++ b/src/map/wall.rs @@ -0,0 +1,98 @@ +use super::Mappable; +use crate::math::{LineSegment, Vec2, Rect}; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; +use std::ops::{Deref, DerefMut}; + +pub type WallData = LineSegment<f64>; + +pub struct Wall { + data: WallData, + round_start: bool, + round_end: bool, +} + +impl Wall { + pub fn from_data(data: WallData, round_start: bool, round_end: bool) -> Self { + Self { + data, + round_start, + round_end, + } + } + + pub fn data(&self) -> &WallData { + &self.data + } +} + +fn draw_round_corner(rld: &mut RaylibDrawHandle, pos_px: Vec2<f64>, transform: &Transform) { + rld.draw_circle_v( + pos_px, + transform.length_m_to_px(0.05) as f32, + Color { + r: 200, + g: 120, + b: 120, + a: 255, + }, + ); +} + +impl Mappable for Wall { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + let start_px = transform.point_m_to_px(&self.data.start); + let end_px = transform.point_m_to_px(&self.data.end); + rld.draw_line_ex( + start_px, + end_px, + transform.length_m_to_px(0.1) as f32, + Color { + r: 200, + g: 120, + b: 120, + a: 255, + }, + ); + + if self.round_start { + draw_round_corner(rld, start_px, transform); + } + if self.round_end { + draw_round_corner(rld, end_px, transform); + } + } + + fn bounding_rect(&self) -> Rect<f64> { + Rect::bounding_rect(self.data.start, self.data.end) + } +} + +impl Scaleable for Wall { + fn scale(&mut self, by: &Vec2<f64>) { + if by.x <= 0. || by.y <= 0. { + panic!("Cannot set dimensions with negative size"); + } + + self.data.start.x *= by.x; + self.data.start.y *= by.y; + self.data.end.x *= by.x; + self.data.end.y *= by.y; + } +} + +impl Deref for Wall { + type Target = WallData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for Wall { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/src/map_data.rs b/src/map_data.rs deleted file mode 100644 index a6ce5a0..0000000 --- a/src/map_data.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::math::{Polygon, Rect, Vec2}; -use crate::tool::icon_tool::IconInfo; -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 the data of the currently opened map. This does not include things that are currently being -/// drawn, but not finished. It also does not contain Metadata, like the current position of the -/// transform, or the zoom-level -#[derive(Serialize, Deserialize)] -pub struct MapData { - rooms: Vec<Rect<f64>>, - polygons: Vec<Polygon<f64>>, - walls: Vec<(Vec2<f64>, Vec2<f64>)>, - icons: Vec<IconInfo>, -} - -impl MapData { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self { - rooms: Vec::new(), - polygons: Vec::new(), - walls: Vec::new(), - icons: Vec::new(), - } - } - - pub fn load_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> { - let file = File::open(&path)?; - let mut data: Self = match from_reader(file) { - Ok(data) => data, - Err(err) => { - return Err(io::Error::new(io::ErrorKind::InvalidData, err)); - } - }; - - /* Append map data to the currently loaded map. (Default behaviour might change in the - * future) - */ - self.rooms.append(&mut data.rooms); - self.polygons.append(&mut data.polygons); - self.walls.append(&mut data.walls); - self.icons.append(&mut data.icons); - - Ok(()) - } - - pub fn write_file<P: AsRef<Path>>(&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()) - } - - pub fn rooms(&self) -> &Vec<Rect<f64>> { - &self.rooms - } - pub fn rooms_mut(&mut self) -> &mut Vec<Rect<f64>> { - &mut self.rooms - } - - pub fn polygons(&self) -> &Vec<Polygon<f64>> { - &self.polygons - } - pub fn polygons_mut(&mut self) -> &mut Vec<Polygon<f64>> { - &mut self.polygons - } - - pub fn walls(&self) -> &Vec<(Vec2<f64>, Vec2<f64>)> { - &self.walls - } - pub fn walls_mut(&mut self) -> &mut Vec<(Vec2<f64>, Vec2<f64>)> { - &mut self.walls - } - - pub fn icons(&self) -> &Vec<IconInfo> { - &self.icons - } - pub fn icons_mut(&mut self) -> &mut Vec<IconInfo> { - &mut self.icons - } -} diff --git a/src/math/line_segment.rs b/src/math/line_segment.rs index 94f58b2..b496787 100644 --- a/src/math/line_segment.rs +++ b/src/math/line_segment.rs @@ -2,9 +2,10 @@ use super::{Rect, Surface, TripletOrientation, Vec2}; use alga::general::{ClosedDiv, ClosedMul, ClosedSub}; use nalgebra::{RealField, Scalar}; use num_traits::Zero; +use serde::{Deserialize, Serialize}; use std::cmp::Ordering; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct LineSegment<T: Scalar + Copy> { pub start: Vec2<T>, pub end: Vec2<T>, diff --git a/src/math/mod.rs b/src/math/mod.rs index 279affc..829a3c5 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -1,34 +1,20 @@ pub mod line_segment; pub mod polygon; pub mod rect; +pub mod surface; pub mod triangle; pub mod vec2; pub use self::line_segment::*; pub use self::polygon::*; pub use self::rect::*; +pub use self::surface::*; pub use self::triangle::*; pub use self::vec2::*; -use nalgebra::Scalar; use num_traits::Float; use std::cmp::Ordering; -/// Trait that describes an area in the vector space on the field of T -pub trait Surface<T: Scalar + Copy> { - /// Checks if a point lies on this surface. - fn contains_point(&self, point: &Vec2<T>) -> bool; - - /// Checks if a line segment is entirely contained by this surface. - fn contains_line_segment(&self, line_segment: &LineSegment<T>) -> bool; - - /// Checks if a rectangle is entirely contained inside this surface. - fn contains_rect(&self, rect: &Rect<T>) -> bool; - - /// Checks if a polygon is contained wholly by this surface. - fn contains_polygon(&self, polygon: &Polygon<T>) -> bool; -} - /// Round a floating point number to the nearest step given by the step argument. For instance, if /// the step is 0.5, then all numbers from 0.0 to 0.24999... will be 0., while all numbers from /// 0.25 to 0.74999... will be 0.5 and so on. diff --git a/src/math/polygon/mod.rs b/src/math/polygon/mod.rs index c9dad91..98b1570 100644 --- a/src/math/polygon/mod.rs +++ b/src/math/polygon/mod.rs @@ -409,6 +409,10 @@ impl< true } + + fn is_inside_rect(&self, rect: &Rect<T>) -> bool { + rect.contains_polygon(&self) + } } /* Helper function to calculate the combined angle of a set of points when connecting them one diff --git a/src/math/rect.rs b/src/math/rect.rs index 5603642..befa4da 100644 --- a/src/math/rect.rs +++ b/src/math/rect.rs @@ -84,6 +84,38 @@ impl<T: Scalar + Copy> Rect<T> { } } + /// Function to calculate the bounding rectangle of n vertices provided. The order of them is + /// not relevant and a point that is contained by the vertices will not change the result. + /// + /// # Panics + /// If there is not at least one vertex in the vertices slice, the function will panic, since it + /// is impossible to calculate any bounds in such a case. + pub fn bounding_rect_n(vertices: &[Vec2<T>]) -> Self + where + T: RealField, + { + if vertices.is_empty() { + panic!("Cannot create bounding rectangle without any vertices"); + } + + let mut min = vertices[0]; + let mut max = vertices[1]; + + for vertex in vertices.iter().skip(1) { + min.x = super::partial_min(min.x, vertex.x); + min.y = super::partial_min(min.y, vertex.y); + max.x = super::partial_max(max.x, vertex.x); + max.y = super::partial_max(max.y, vertex.y); + } + + Self { + x: min.x, + y: min.y, + w: max.x - min.x, + h: max.y - min.y + } + } + /// Get the shortest way that must be applied to this Rect to clear out of /// another Rect of the same type so that they would not intersect any more. pub fn shortest_way_out(&self, of: &Rect<T>) -> Vec2<T> @@ -145,6 +177,10 @@ impl<T: Scalar + Copy + PartialOrd + ClosedAdd + ClosedSub + Zero> Surface<T> fo .iter() .all(|&corner| self.contains_point(&corner)) } + + fn is_inside_rect(&self, rect: &Rect<T>) -> bool { + rect.contains_rect(&self) + } } // This is sad, but also sadly necessary :/ diff --git a/src/math/surface.rs b/src/math/surface.rs new file mode 100644 index 0000000..da265d8 --- /dev/null +++ b/src/math/surface.rs @@ -0,0 +1,21 @@ +use super::{LineSegment, Polygon, Rect, Vec2}; +use nalgebra::Scalar; + +/// Trait that describes an area in the vector space on the field of T +pub trait Surface<T: Scalar + Copy> { + /// Checks if a point lies on this surface. + fn contains_point(&self, point: &Vec2<T>) -> bool; + + /// Checks if a line segment is entirely contained by this surface. + fn contains_line_segment(&self, line_segment: &LineSegment<T>) -> bool; + + /// Checks if a rectangle is entirely contained inside this surface. + fn contains_rect(&self, rect: &Rect<T>) -> bool; + + /// Checks if a polygon is contained wholly by this surface. + fn contains_polygon(&self, polygon: &Polygon<T>) -> bool; + + /// Checks if this surface is contained by the rect in it's entirety. Think of it as the reverse + /// operation for contains_... on a rectangle. + fn is_inside_rect(&self, rect: &Rect<T>) -> bool; +} diff --git a/src/scaleable.rs b/src/scaleable.rs new file mode 100644 index 0000000..450e61e --- /dev/null +++ b/src/scaleable.rs @@ -0,0 +1,11 @@ +use crate::math::Vec2; + +/// Trait representing something that covers an area and that can be resized accordingly. +pub trait Scaleable { + /// Scale the object by the specified amount in horizontal and vertical direction (right and + /// downwards). + /// + /// # Panics + /// If at least one of the dimensions is zero or less, the object cannot be scaled and panics. + fn scale(&mut self, by: &Vec2<f64>); +} diff --git a/src/tool/deletion_tool.rs b/src/tool/deletion_tool.rs index 74a8f92..ad3af1d 100644 --- a/src/tool/deletion_tool.rs +++ b/src/tool/deletion_tool.rs @@ -1,41 +1,25 @@ use super::Tool; -use crate::button::Button; -use crate::config::{DeletionToolKeybindings, ToolKeybindings}; -use crate::map_data::MapData; +use crate::map::Map; use crate::math::{Rect, Surface, Vec2}; use crate::transform::Transform; use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; use raylib::ffi::Color; -use raylib::RaylibHandle; pub struct DeletionTool { - keybindings: DeletionToolKeybindings, deletion_rect: Option<(Vec2<f64>, Vec2<f64>)>, } impl DeletionTool { - pub fn new(keybindings: DeletionToolKeybindings) -> Self { + pub fn new() -> Self { Self { - keybindings, deletion_rect: None, } } +} - /// Delete all map-data that is contained inside the provided rectangular space. - pub fn delete_rect(map_data: &mut MapData, rect: Rect<f64>) { - map_data - .rooms_mut() - .retain(|&room| !rect.contains_rect(&room)); - map_data - .walls_mut() - .retain(|&(pos1, pos2)| !rect.contains_point(&pos1) || !rect.contains_point(&pos2)); - map_data - .icons_mut() - .retain(|icon| !rect.contains_point(&icon.position)); - map_data - .polygons_mut() - .retain(|polygon| !rect.contains_polygon(&polygon)); - } +fn delete_rect((pos1, pos2): (&Vec2<f64>, &Vec2<f64>), map: &mut Map) { + let bounds = Rect::bounding_rect(*pos1, *pos2); + map.retain(|e| !bounds.contains_rect(&e.bounding_rect())); } impl Tool for DeletionTool { @@ -43,34 +27,13 @@ impl Tool for DeletionTool { self.deletion_rect = None; } - fn active_update( - &mut self, - map_data: &mut MapData, - rl: &RaylibHandle, - transform: &Transform, - mouse_blocked: bool, - ) { - let mouse_pos_m = transform.point_px_to_m(&rl.get_mouse_position().into()); + fn update(&mut self, _map: &Map, mouse_pos_m: &Vec2<f64>) { if let Some((_, ref mut pos2)) = &mut self.deletion_rect { - *pos2 = mouse_pos_m; - } - - if self.keybindings.do_delete.is_pressed(rl, mouse_blocked) && self.deletion_rect.is_some() - { - let (pos1, pos2) = self.deletion_rect.take().unwrap(); - Self::delete_rect(map_data, Rect::bounding_rect(pos1, pos2)); - } else if self - .keybindings - .start_selection - .is_pressed(rl, mouse_blocked) - { - self.deletion_rect = Some((mouse_pos_m, mouse_pos_m)) - } else if self.keybindings.abort_deletion.is_pressed(rl, false) { - self.deletion_rect = None; + *pos2 = *mouse_pos_m; } } - fn draw(&self, _map_data: &MapData, rld: &mut RaylibDrawHandle, transform: &Transform) { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { if let Some((pos1, pos2)) = self.deletion_rect { let rect_px = transform.rect_m_to_px(&Rect::bounding_rect(pos1, pos2)); rld.draw_rectangle_rec( @@ -95,7 +58,23 @@ impl Tool for DeletionTool { } } - fn activation_key(&self) -> Button { - self.keybindings.activation_key() + fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) { + if let Some((pos1, pos2)) = self.deletion_rect { + delete_rect((&pos1, &pos2), map); + self.deletion_rect = None; + } else { + self.deletion_rect = Some((*mouse_pos_m, *mouse_pos_m)); + } + } + + fn finish(&mut self, map: &mut Map) { + if let Some((pos1, pos2)) = self.deletion_rect { + delete_rect((&pos1, &pos2), map); + self.deletion_rect = None; + } + } + + fn abort(&mut self) { + self.deletion_rect = None; } } diff --git a/src/tool/icon_tool.rs b/src/tool/icon_tool.rs index e972c1c..09b0ac1 100644 --- a/src/tool/icon_tool.rs +++ b/src/tool/icon_tool.rs @@ -1,115 +1,33 @@ use crate::button::Button; -use crate::config::{IconToolKeybindings, ToolKeybindings}; -use crate::grid::{snap_to_grid, SNAP_SIZE}; -use crate::map_data::MapData; +use crate::config::IconToolKeys; +use crate::map::icon_renderer::IconRenderer; +use crate::map::{Icon, Map, Mappable}; 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}; +use raylib::core::drawing::RaylibDrawHandle; +use std::rc::Rc; 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<f64>, - /// The scale of the icon as expressed in image pixels per real meter. - pixels_per_m: f64, -} - -#[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<f64>, - /// Rotation of the icon texture in degrees. - pub rotation: f64, -} - 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)>, + keybindings: IconToolKeys, /// 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., - } - } + current_icon: Icon, + renderer: Rc<IconRenderer>, } 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)); - } - + pub fn new(keybindings: IconToolKeys, renderer: Rc<IconRenderer>) -> Self { Self { keybindings, - icon_data, active: false, - current_icon: IconInfo::default(), + current_icon: Icon::new(0, Vec2::default(), 0., renderer.clone()), + renderer, } } } @@ -123,85 +41,30 @@ impl Tool for IconTool { self.active = false; } - fn active_update( - &mut self, - map: &mut MapData, - rl: &RaylibHandle, - transform: &Transform, - mouse_blocked: bool, - ) { - // 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, mouse_blocked) { - self.current_icon.icon_id = (self.current_icon.icon_id + 1) % self.icon_data.len(); - } - if self - .keybindings - .rotate_clockwise - .is_pressed(rl, mouse_blocked) - { - self.current_icon.rotation += 45.; - } - - // Handle placing the icon on the map - if self.keybindings.place.is_pressed(rl, mouse_blocked) { - map.icons_mut().push(self.current_icon.clone()); - } + fn update(&mut self, _map: &Map, mouse_pos_m: &Vec2<f64>) { + self.current_icon.position = *mouse_pos_m; } - 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.floor(); - position_px.y = position_px.y.floor(); - rld.draw_texture_ex( - texture, - position_px, - icon.rotation as f32, - (transform.pixels_per_m() / info.pixels_per_m) as f32, - Color { - r: 255, - g: 255, - b: 255, - a: 255, - }, - ); - } - - // Draw the icon that would be placed + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { 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.floor(); - position_px.y = position_px.y.floor(); - rld.draw_texture_ex( - texture, - position_px, - self.current_icon.rotation as f32, - (transform.pixels_per_m() / info.pixels_per_m) as f32, - Color { - r: 120, - g: 200, - b: 120, - a: 255, - }, - ); + self.current_icon.draw(rld, transform); } } - fn activation_key(&self) -> Button { - self.keybindings.activation_key() + fn place_single(&mut self, map: &mut Map, _mouse_pos_m: &Vec2<f64>) { + map.push_icon(self.current_icon.clone()); + } + + fn on_button_pressed(&mut self, _map: &mut Map, button: Button) { + if button == self.keybindings.next { + self.current_icon.id = (self.current_icon.id + 1) % self.renderer.num_icons(); + } else if button == self.keybindings.previous { + self.current_icon.id = + (self.current_icon.id + self.renderer.num_icons() - 1) % self.renderer.num_icons(); + } else if button == self.keybindings.rotate_clockwise { + self.current_icon.rotation += 45.; + } else if button == self.keybindings.rotate_counterclockwise { + self.current_icon.rotation -= 45.; + } } } diff --git a/src/tool/mod.rs b/src/tool/mod.rs index f503d2b..532572a 100644 --- a/src/tool/mod.rs +++ b/src/tool/mod.rs @@ -1,25 +1,25 @@ pub mod deletion_tool; pub mod icon_tool; pub mod polygon_room_tool; -pub mod room_tool; +pub mod rect_room_tool; pub mod wall_tool; pub use deletion_tool::DeletionTool; pub use icon_tool::IconTool; pub use polygon_room_tool::PolygonRoomTool; -pub use room_tool::RoomTool; +pub use rect_room_tool::RectRoomTool; pub use wall_tool::WallTool; use crate::button::Button; -use crate::map_data::MapData; +use crate::map::Map; +use crate::math::Vec2; use crate::transform::Transform; use raylib::core::drawing::RaylibDrawHandle; -use raylib::RaylibHandle; -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[repr(u8)] pub enum ToolType { - RoomTool, + RectRoomTool, PolygonRoomTool, WallTool, IconTool, @@ -28,19 +28,33 @@ pub enum ToolType { } pub trait Tool { + /// Code that needs to be called when this Tool is activated or reactivated goes here. fn activate(&mut self) {} + /// Cleanup that needs to be done when the user switches from this tool to something else goes here. fn deactivate(&mut self) {} - fn update(&mut self, _map: &MapData, _rl: &RaylibHandle, _transform: &Transform) {} - fn active_update( - &mut self, - map: &mut MapData, - rl: &RaylibHandle, - transform: &Transform, - mouse_blocked: bool, - ); + /// Called on each frame when this tool is active. + fn update(&mut self, map: &Map, mouse_pos_m: &Vec2<f64>); - fn draw(&self, _map: &MapData, _rld: &mut RaylibDrawHandle, _transform: &Transform) {} + /// Draw the contents of this tool. + // TODO: Maybe make this tool mappable? This might make it easier to make things resizable while + // it's still being drawn. + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform); - fn activation_key(&self) -> Button; + /// Generic keybinding. + /// Code to place a single node for this tool. + fn place_single(&mut self, _map: &mut Map, _mouse_pos_m: &Vec2<f64>) {} + + /// Generic keybinding. + /// Code to finish whatever one is doing with this tool currently and trying to apply the + /// changes to the map data. + fn finish(&mut self, _map: &mut Map) {} + + /// Generic keybinding. + /// Stop whatever one is doing with this tool and do not apply any changes to the map data. + fn abort(&mut self) {} + + /// If there are any additional keybindings that need to be handled by this tool, these can be + /// handled here. + fn on_button_pressed(&mut self, _map: &mut Map, _button: Button) {} } diff --git a/src/tool/polygon_room_tool.rs b/src/tool/polygon_room_tool.rs index 8cd2c25..4f8edce 100644 --- a/src/tool/polygon_room_tool.rs +++ b/src/tool/polygon_room_tool.rs @@ -1,14 +1,9 @@ use super::Tool; -use crate::button::Button; -use crate::config::{PolygonRoomToolKeybindings, ToolKeybindings}; -use crate::dimension_indicator::DimensionIndicator; -use crate::grid::{snap_to_grid, SNAP_SIZE}; -use crate::map_data::MapData; +use crate::map::Map; use crate::math::{self, Polygon, PolygonError, Vec2}; use crate::transform::Transform; use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; use raylib::ffi::Color; -use raylib::RaylibHandle; struct UnfinishedPolygon { pub corners: Vec<Vec2<f64>>, @@ -16,9 +11,7 @@ struct UnfinishedPolygon { } pub struct PolygonRoomTool { - keybindings: PolygonRoomToolKeybindings, unfinished_polygon: Option<UnfinishedPolygon>, - dimension_indicator: DimensionIndicator, } impl UnfinishedPolygon { @@ -83,108 +76,26 @@ impl UnfinishedPolygon { } impl PolygonRoomTool { - pub fn new(keybindings: PolygonRoomToolKeybindings) -> Self { + pub fn new() -> Self { Self { - keybindings, unfinished_polygon: None, - dimension_indicator: DimensionIndicator::new(), } } } impl Tool for PolygonRoomTool { - fn activate(&mut self) {} - fn deactivate(&mut self) { self.unfinished_polygon = None; } - fn active_update( - &mut self, - map: &mut MapData, - rl: &RaylibHandle, - transform: &Transform, - mouse_blocked: bool, - ) { - let mouse_pos_m = transform.point_px_to_m(&rl.get_mouse_position().into()); - let snapped_mouse_pos_m = snap_to_grid(mouse_pos_m, SNAP_SIZE); - + fn update(&mut self, _map: &Map, mouse_pos_m: &Vec2<f64>) { // Update the position of the node that would be placed into the polygon next. if let Some(ref mut polygon) = &mut self.unfinished_polygon { - polygon.working_corner = snapped_mouse_pos_m; - - polygon.corners.push(polygon.working_corner); - self.dimension_indicator.update_dimensions(&polygon.corners); - polygon.working_corner = polygon.corners.pop().unwrap(); - } - - /* Check if the finishing keybinding has been pressed. If so, try to turn the part of the - * polygon that is already completed into a proper polygon and push it into the map data. - */ - if self.keybindings.finish.is_pressed(rl, mouse_blocked) { - if let Some(ref mut polygon) = self.unfinished_polygon { - if let Some(polygon) = polygon.try_into_completed() { - self.dimension_indicator.clear_dimensions(); - map.polygons_mut().push(polygon); - self.unfinished_polygon = None; - } - } - } - - /* Handle placing a new corner of the polygon. If the corner is placed on the first node, - * the polygon will be created. - */ - if self.keybindings.place_node.is_pressed(rl, mouse_blocked) { - if let Some(ref mut polygon) = &mut self.unfinished_polygon { - if polygon.working_corner == polygon.corners[0] { - /* The working corner will be ignored, since it would double the vertex at the - * polygon starting position. - */ - if let Some(polygon) = polygon.try_into_completed() { - self.dimension_indicator.clear_dimensions(); - map.polygons_mut().push(polygon); - self.unfinished_polygon = None; - } - } else { - // Check if we can add the corner to the polygon without ruining it. - if let Err(e) = polygon.try_push_working() { - error!("Cannot add corner to polygon: {}", e); - } - } - } else { - // Start a new unfinished polygon - self.unfinished_polygon = Some(UnfinishedPolygon { - corners: vec![snapped_mouse_pos_m], - working_corner: snapped_mouse_pos_m, - }); - } - } - - if self.keybindings.abort.is_pressed(rl, false) { - self.unfinished_polygon = None; + polygon.working_corner = *mouse_pos_m; } } - fn draw(&self, map: &MapData, rld: &mut RaylibDrawHandle, transform: &Transform) { - // TODO: Buffer triangles so the polygons don't always have to be retriangulated. - for polygon in map.polygons() { - let triangles = math::triangulate(polygon.clone()); - for triangle in triangles { - let triangle: [Vec2<f64>; 3] = triangle.into(); - rld.draw_triangle( - transform.point_m_to_px(&triangle[0]), - transform.point_m_to_px(&triangle[1]), - transform.point_m_to_px(&triangle[2]), - Color { - r: 180, - g: 180, - b: 180, - a: 255, - }, - ) - } - } - + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { if let Some(polygon) = &self.unfinished_polygon { // The first corner is guaranteed to be set, so we can at least draw a line. if polygon.corners.len() == 1 { @@ -252,12 +163,44 @@ impl Tool for PolygonRoomTool { } } } + } + } + + fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) { + if let Some(ref mut polygon) = &mut self.unfinished_polygon { + if polygon.working_corner == polygon.corners[0] { + /* The working corner will be ignored, since it would double the vertex at the + * polygon starting position. + */ + if let Some(polygon) = polygon.try_into_completed() { + map.push_polygon_room(polygon); + self.unfinished_polygon = None; + } + } else { + // Check if we can add the corner to the polygon without ruining it. + if let Err(e) = polygon.try_push_working() { + error!("Cannot add corner to polygon: {}", e); + } + } + } else { + // Start a new unfinished polygon + self.unfinished_polygon = Some(UnfinishedPolygon { + corners: vec![*mouse_pos_m], + working_corner: *mouse_pos_m, + }); + } + } - self.dimension_indicator.draw(rld, transform); + fn finish(&mut self, map: &mut Map) { + if let Some(ref mut polygon) = self.unfinished_polygon { + if let Some(polygon) = polygon.try_into_completed() { + map.push_polygon_room(polygon); + self.unfinished_polygon = None; + } } } - fn activation_key(&self) -> Button { - self.keybindings.activation_key() + fn abort(&mut self) { + self.unfinished_polygon = None; } } diff --git a/src/tool/rect_room_tool.rs b/src/tool/rect_room_tool.rs new file mode 100644 index 0000000..3eb40aa --- /dev/null +++ b/src/tool/rect_room_tool.rs @@ -0,0 +1,78 @@ +use super::Tool; +use crate::map::Map; +use crate::math::{Rect, Vec2}; +use crate::transform::Transform; +use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; + +pub struct RectRoomTool { + /// The rectangle that is currently being drawn by the user. Once it is finished, it will be + /// pushed into the room_rects. + unfinished_rect: Option<(Vec2<f64>, Vec2<f64>)>, +} + +impl RectRoomTool { + /// Create a new room tool where no rooms have been drawn yet. + pub fn new() -> Self { + Self { + unfinished_rect: None, + } + } +} + +impl Tool for RectRoomTool { + fn deactivate(&mut self) { + self.unfinished_rect = None; + } + + fn update(&mut self, _map: &Map, mouse_pos_m: &Vec2<f64>) { + if let Some((_, ref mut pos2)) = &mut self.unfinished_rect { + *pos2 = *mouse_pos_m; + } + } + + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + if let Some((pos1, pos2)) = self.unfinished_rect { + rld.draw_rectangle_rec( + transform.rect_m_to_px(&Rect::bounding_rect(pos1, pos2)), + Color { + r: 150, + g: 200, + b: 150, + a: 255, + }, + ); + } + } + + fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) { + // Try to finish the rectangle if it has been started. + if let Some((pos1, pos2)) = self.unfinished_rect { + if pos1 == pos2 { + warn!("Cannot place rectangle with start and endpoint being the same"); + return; + } + + map.push_rect_room(Rect::bounding_rect(pos1, pos2)); + self.unfinished_rect = None; + } else { + self.unfinished_rect = Some((*mouse_pos_m, *mouse_pos_m)); + } + } + + fn finish(&mut self, map: &mut Map) { + if let Some((pos1, pos2)) = self.unfinished_rect { + if pos1 == pos2 { + warn!("Cannot place rectangle with start and endpoint being the same"); + return; + } + + map.push_rect_room(Rect::bounding_rect(pos1, pos2)); + self.unfinished_rect = None; + } + } + + fn abort(&mut self) { + self.unfinished_rect = None; + } +} diff --git a/src/tool/room_tool.rs b/src/tool/room_tool.rs deleted file mode 100644 index 6a283e3..0000000 --- a/src/tool/room_tool.rs +++ /dev/null @@ -1,103 +0,0 @@ -use super::Tool; -use crate::button::Button; -use crate::config::{RoomToolKeybindings, ToolKeybindings}; -use crate::dimension_indicator::DimensionIndicator; -use crate::grid::{snap_to_grid, SNAP_SIZE}; -use crate::map_data::MapData; -use crate::math::{Rect, Vec2}; -use crate::transform::Transform; -use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; -use raylib::ffi::Color; -use raylib::RaylibHandle; - -pub struct RoomTool { - keybindings: RoomToolKeybindings, - /// The rectangle that is currently being drawn by the user. Once it is finished, it will be - /// pushed into the room_rects. - unfinished_rect: Option<(Vec2<f64>, Vec2<f64>)>, - dimension_indicator: DimensionIndicator, -} - -impl RoomTool { - /// Create a new room tool where no rooms have been drawn yet. - pub fn new(keybindings: RoomToolKeybindings) -> Self { - Self { - keybindings, - unfinished_rect: None, - dimension_indicator: DimensionIndicator::new(), - } - } -} - -impl Tool for RoomTool { - fn deactivate(&mut self) { - self.unfinished_rect = None; - self.dimension_indicator.clear_dimensions(); - } - - fn active_update( - &mut self, - map_data: &mut MapData, - rl: &RaylibHandle, - transform: &Transform, - mouse_blocked: bool, - ) { - let mouse_pos_m = transform.point_px_to_m(&rl.get_mouse_position().into()); - // Update the currently drawn rectangle, if it exists, and also its dimension indicator. - if let Some((ref pos1, ref mut pos2)) = &mut self.unfinished_rect { - *pos2 = snap_to_grid(mouse_pos_m, SNAP_SIZE); - - self.dimension_indicator.update_dimensions(&[*pos1, *pos2]); - } - - // Start or finish drawing the currently unfinished rectangle - if self.keybindings.finish_draw.is_pressed(rl, mouse_blocked) - && self.unfinished_rect.is_some() - { - let (pos1, pos2) = self.unfinished_rect.take().unwrap(); - self.dimension_indicator.clear_dimensions(); - map_data.rooms_mut().push(Rect::bounding_rect(pos1, pos2)); - } else if self.keybindings.start_draw.is_pressed(rl, mouse_blocked) { - let snapped_mouse_pos = snap_to_grid(mouse_pos_m, SNAP_SIZE); - self.unfinished_rect = Some((snapped_mouse_pos, snapped_mouse_pos)) - } - - if self.keybindings.abort_draw.is_pressed(rl, false) { - self.unfinished_rect = None; - } - } - - fn draw(&self, map_data: &MapData, rld: &mut RaylibDrawHandle, transform: &Transform) { - // Draw all finished rectangles. - for &rect in map_data.rooms() { - rld.draw_rectangle_rec( - transform.rect_m_to_px(&rect), - Color { - r: 180, - g: 180, - b: 180, - a: 255, - }, - ); - } - - // Do the same for the unfinished rectangle - if let Some((pos1, pos2)) = self.unfinished_rect { - rld.draw_rectangle_rec( - transform.rect_m_to_px(&Rect::bounding_rect(pos1, pos2)), - Color { - r: 150, - g: 200, - b: 150, - a: 255, - }, - ); - - self.dimension_indicator.draw(rld, transform); - } - } - - fn activation_key(&self) -> Button { - self.keybindings.activation_key() - } -} diff --git a/src/tool/wall_tool.rs b/src/tool/wall_tool.rs index d86d0af..b958799 100644 --- a/src/tool/wall_tool.rs +++ b/src/tool/wall_tool.rs @@ -1,23 +1,17 @@ use super::Tool; -use crate::button::Button; -use crate::config::{ToolKeybindings, WallToolKeybindings}; -use crate::grid::{snap_to_grid, SNAP_SIZE}; -use crate::map_data::MapData; -use crate::math::Vec2; +use crate::map::Map; +use crate::math::{LineSegment, Vec2}; use crate::transform::Transform; use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; use raylib::ffi::{Color, Vector2}; -use raylib::RaylibHandle; pub struct WallTool { - keybindings: WallToolKeybindings, - unfinished_wall: Option<(Vec2<f64>, Vec2<f64>)>, + unfinished_wall: Option<LineSegment<f64>>, } impl WallTool { - pub fn new(keybindings: WallToolKeybindings) -> Self { + pub fn new() -> Self { Self { - keybindings, unfinished_wall: None, } } @@ -28,86 +22,19 @@ impl Tool for WallTool { self.unfinished_wall = None; } - fn active_update( - &mut self, - map_data: &mut MapData, - rl: &RaylibHandle, - transform: &Transform, - mouse_blocked: bool, - ) { - let mouse_pos_m = transform.point_px_to_m(&rl.get_mouse_position().into()); - if let Some((_, ref mut pos2)) = &mut self.unfinished_wall { - *pos2 = snap_to_grid(mouse_pos_m, SNAP_SIZE); - } - - if self - .keybindings - .finish_segment - .is_pressed(rl, mouse_blocked) - && self.unfinished_wall.is_some() - { - let (pos1, pos2) = self.unfinished_wall.unwrap(); - map_data.walls_mut().push((pos1, pos2)); - self.unfinished_wall = Some((pos2, pos2)); - } else if self.keybindings.start_wall.is_pressed(rl, mouse_blocked) { - let snapped_mouse_pos = snap_to_grid(mouse_pos_m, SNAP_SIZE); - self.unfinished_wall = Some((snapped_mouse_pos, snapped_mouse_pos)) - } - - if self.keybindings.abort_segment.is_pressed(rl, false) { - self.unfinished_wall = None; + fn update(&mut self, _map: &Map, mouse_pos_m: &Vec2<f64>) { + if let Some(ref mut wall) = &mut self.unfinished_wall { + wall.end = *mouse_pos_m; } } - fn draw(&self, map_data: &MapData, rld: &mut RaylibDrawHandle, transform: &Transform) { - for &(pos1, pos2) in map_data.walls() { - let pos1_px = transform.point_m_to_px(&pos1); - let pos2_px = transform.point_m_to_px(&pos2); - rld.draw_line_ex( - pos1_px, - pos2_px, - transform.length_m_to_px(0.1) as f32, - Color { - r: 200, - g: 120, - b: 120, - a: 255, - }, - ); - - /* Find walls that end/start at the start or end of this wall and draw part of a circle - * to join these two walls more nicely. - */ - for &(other1, other2) in map_data.walls() { - // Ignore the line segment if it's the same wall - if pos1 == other1 && pos2 == other2 { - continue; - } - - // TODO: Only draw segments when introducing transparency. - for pos in [pos1, pos2].iter() { - if *pos == other1 || *pos == other2 { - rld.draw_circle_v( - transform.point_m_to_px(&pos), - transform.length_m_to_px(0.05) as f32, - Color { - r: 200, - g: 120, - b: 120, - a: 255, - }, - ); - } - } - } - } - - if let Some((pos1, pos2)) = self.unfinished_wall { - let pos1: Vector2 = transform.point_m_to_px(&pos1).into(); - let pos2: Vector2 = transform.point_m_to_px(&pos2).into(); + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + if let Some(ref wall) = self.unfinished_wall { + let start: Vector2 = transform.point_m_to_px(&wall.start).into(); + let end: Vector2 = transform.point_m_to_px(&wall.end).into(); rld.draw_line_ex( - pos1, - pos2, + start, + end, transform.length_m_to_px(0.1) as f32, Color { r: 150, @@ -119,7 +46,17 @@ impl Tool for WallTool { } } - fn activation_key(&self) -> Button { - self.keybindings.activation_key() + fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) { + if let Some(wall) = self.unfinished_wall.take() { + // Continue with the next wall straight away. + self.unfinished_wall = Some(LineSegment::new(wall.end, wall.end)); + map.push_wall(wall); + } else { + self.unfinished_wall = Some(LineSegment::new(*mouse_pos_m, *mouse_pos_m)); + } + } + + fn abort(&mut self) { + self.unfinished_wall = None; } } |
