aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Dußin2020-12-21 21:12:01 +0100
committerGitHub2020-12-21 21:12:01 +0100
commitc435f278eddcada279fdc424120e4a1448843c20 (patch)
treebe9a5601e99608966d4ccd146c3bfb3a70c7fc02
parent3bc690803fb59493ea8180fd630d65b3e26642d0 (diff)
parent82d11b7d3e15d8175accf7579db1fbe528fc6583 (diff)
downloadgraf_karto-c435f278eddcada279fdc424120e4a1448843c20.tar.gz
graf_karto-c435f278eddcada279fdc424120e4a1448843c20.zip
Merge pull request #24 from LordSentox/refactor
Refactor to make interaction between tools easier
-rw-r--r--.gitignore1
-rw-r--r--src/colours.rs86
-rw-r--r--src/config.rs125
-rw-r--r--src/editor.rs163
-rw-r--r--src/gui/dimension_indicator.rs128
-rw-r--r--src/main.rs14
-rw-r--r--src/map/data.rs64
-rw-r--r--src/map/icon.rs94
-rw-r--r--src/map/icon_renderer.rs77
-rw-r--r--src/map/mappable.rs27
-rw-r--r--src/map/mod.rs122
-rw-r--r--src/map/polygon_room.rs77
-rw-r--r--src/map/rect_room.rs79
-rw-r--r--src/map/wall.rs111
-rw-r--r--src/map_data.rs97
-rw-r--r--src/math/line_segment.rs3
-rw-r--r--src/math/mod.rs18
-rw-r--r--src/math/polygon/mod.rs4
-rw-r--r--src/math/rect.rs36
-rw-r--r--src/math/surface.rs21
-rw-r--r--src/scaleable.rs11
-rw-r--r--src/tool/deletion_tool.rs91
-rw-r--r--src/tool/icon_tool.rs197
-rw-r--r--src/tool/mod.rs49
-rw-r--r--src/tool/polygon_room_tool.rs167
-rw-r--r--src/tool/rect_room_tool.rs73
-rw-r--r--src/tool/room_tool.rs103
-rw-r--r--src/tool/selection_tool.rs63
-rw-r--r--src/tool/wall_tool.rs113
-rw-r--r--uml/class_diagram.uml6
30 files changed, 1413 insertions, 807 deletions
diff --git a/.gitignore b/.gitignore
index 61b94ce..6103465 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/target
swap.ron
config.ron
+/.idea
diff --git a/src/colours.rs b/src/colours.rs
new file mode 100644
index 0000000..8d69869
--- /dev/null
+++ b/src/colours.rs
@@ -0,0 +1,86 @@
+use raylib::ffi::Color;
+
+pub const DEFAULT_COLOURS: Colours = Colours::default();
+
+pub struct Colours {
+ pub deletion_rect: Color,
+ pub deletion_rect_outline: Color,
+ pub selection_rect: Color,
+ pub selection_rect_outline: Color,
+ pub room_normal: Color,
+ pub room_selected: Color,
+ pub wall_normal: Color,
+ pub wall_selected: Color,
+ pub icon_normal: Color,
+ pub icon_selected: Color,
+}
+
+impl Colours {
+ // NOTE: Unfortunately the default function cannot be made const, since Default is a trait. This
+ // feature is, as far as I can tell, planned in Rust, but not yet implemented. Once it is, Colours
+ // should implement Default instead.
+ const fn default() -> Self {
+ Self {
+ deletion_rect: Color {
+ r: 200,
+ g: 150,
+ b: 150,
+ a: 50,
+ },
+ deletion_rect_outline: Color {
+ r: 200,
+ g: 150,
+ b: 150,
+ a: 150,
+ },
+ selection_rect: Color {
+ r: 255,
+ g: 129,
+ b: 0,
+ a: 50,
+ },
+ selection_rect_outline: Color {
+ r: 255,
+ g: 129,
+ b: 0,
+ a: 150,
+ },
+ room_normal: Color {
+ r: 180,
+ g: 180,
+ b: 180,
+ a: 255,
+ },
+ room_selected: Color {
+ r: 150,
+ g: 200,
+ b: 150,
+ a: 255,
+ },
+ wall_normal: Color {
+ r: 200,
+ g: 120,
+ b: 120,
+ a: 255,
+ },
+ wall_selected: Color {
+ r: 150,
+ g: 200,
+ b: 150,
+ a: 255,
+ },
+ icon_normal: Color {
+ r: 255,
+ g: 255,
+ b: 255,
+ a: 255,
+ },
+ icon_selected: Color {
+ r: 150,
+ g: 200,
+ b: 150,
+ a: 255,
+ },
+ }
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index b4703a2..2a1e5ed 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -8,58 +8,34 @@ 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 selection: 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 +69,25 @@ 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),
+ selection: Button::Keyboard(KeyboardKey::S),
+ 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..e6a2dcb 100644
--- a/src/editor.rs
+++ b/src/editor.rs
@@ -1,71 +1,115 @@
+use crate::button::{Button, MouseButton};
use crate::config::Config;
-use crate::map_data::MapData;
+use crate::grid::{snap_to_grid, SNAP_SIZE};
+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;
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,
+ ),
+ );
+ tools.insert(
+ ToolType::SelectionTool,
+ (
+ Box::new(SelectionTool::new()),
+ config.tool_activation_keys.selection,
+ ),
+ );
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 +120,68 @@ 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);
+
+ // 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);
- for tool in &mut self.tools {
- tool.update(&self.map_data, rl, transform);
+ // 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();
}
- self.tools[self.active].active_update(&mut self.map_data, rl, transform, mouse_blocked);
+ // 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()
+ };
+
+ 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..8ddc587 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,13 +4,15 @@
extern crate log;
pub mod button;
+pub mod colours;
pub mod config;
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 +77,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 +91,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..f623c98
--- /dev/null
+++ b/src/map/icon.rs
@@ -0,0 +1,94 @@
+use super::icon_renderer::IconRenderer;
+use crate::colours::DEFAULT_COLOURS;
+use crate::map::Mappable;
+use crate::math::{Rect, Vec2};
+use crate::transform::Transform;
+use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
+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,
+ selected: bool,
+ 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,
+ selected: false,
+ 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,
+ if self.selected() {
+ DEFAULT_COLOURS.icon_selected
+ } else {
+ DEFAULT_COLOURS.icon_normal
+ },
+ );
+ }
+
+ fn set_selected(&mut self, selected: bool) {
+ self.selected = selected;
+ }
+
+ fn selected(&self) -> bool {
+ self.selected
+ }
+
+ 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..b348c4b
--- /dev/null
+++ b/src/map/mappable.rs
@@ -0,0 +1,27 @@
+//! 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);
+
+ /// Set the selection status of this item. If it is selected, actions that concern all selected
+ /// items will be applied to this item as well.
+ fn set_selected(&mut self, selected: bool);
+
+ /// Get if this item is currently selected.
+ fn selected(&self) -> bool;
+
+ /// 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..ff03474
--- /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.elements() {
+ 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 elements(&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 elements_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..a209a7c
--- /dev/null
+++ b/src/map/polygon_room.rs
@@ -0,0 +1,77 @@
+use super::Mappable;
+use crate::colours::DEFAULT_COLOURS;
+use crate::math::{self, Polygon, Rect, Triangle, Vec2};
+use crate::scaleable::Scaleable;
+use crate::transform::Transform;
+use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
+
+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>>,
+ selected: bool,
+}
+
+impl PolygonRoom {
+ pub fn from_data(data: PolygonRoomData) -> Self {
+ Self {
+ data: data.clone(),
+ triangulated: math::triangulate(data),
+ selected: false,
+ }
+ }
+
+ 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]),
+ if self.selected() {
+ DEFAULT_COLOURS.room_selected
+ } else {
+ DEFAULT_COLOURS.room_normal
+ },
+ )
+ }
+ }
+
+ fn set_selected(&mut self, selected: bool) {
+ self.selected = selected;
+ }
+
+ fn selected(&self) -> bool {
+ self.selected
+ }
+
+ 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..5008c63
--- /dev/null
+++ b/src/map/rect_room.rs
@@ -0,0 +1,79 @@
+use crate::colours::DEFAULT_COLOURS;
+use crate::map::Mappable;
+use crate::math::{Rect, Vec2};
+use crate::scaleable::Scaleable;
+use crate::transform::Transform;
+use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
+use serde::Serialize;
+use std::ops::{Deref, DerefMut};
+
+pub type RectRoomData = Rect<f64>;
+
+#[derive(Serialize)]
+pub struct RectRoom {
+ data: RectRoomData,
+ selected: bool,
+}
+
+impl RectRoom {
+ pub fn from_data(data: RectRoomData) -> Self {
+ RectRoom {
+ data,
+ selected: false,
+ }
+ }
+}
+
+impl Mappable for RectRoom {
+ fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
+ rld.draw_rectangle_rec(
+ transform.rect_m_to_px(&self.data),
+ if self.selected() {
+ DEFAULT_COLOURS.room_selected
+ } else {
+ DEFAULT_COLOURS.room_normal
+ },
+ );
+ }
+
+ fn set_selected(&mut self, selected: bool) {
+ self.selected = selected;
+ }
+
+ fn selected(&self) -> bool {
+ self.selected
+ }
+
+ 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..22393bb
--- /dev/null
+++ b/src/map/wall.rs
@@ -0,0 +1,111 @@
+use super::Mappable;
+use crate::colours::DEFAULT_COLOURS;
+use crate::math::{LineSegment, Rect, Vec2};
+use crate::scaleable::Scaleable;
+use crate::transform::Transform;
+use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
+use std::ops::{Deref, DerefMut};
+
+pub type WallData = LineSegment<f64>;
+
+pub struct Wall {
+ data: WallData,
+ selected: bool,
+ round_start: bool,
+ round_end: bool,
+}
+
+impl Wall {
+ pub fn from_data(data: WallData, round_start: bool, round_end: bool) -> Self {
+ Self {
+ data,
+ selected: false,
+ round_start,
+ round_end,
+ }
+ }
+
+ pub fn data(&self) -> &WallData {
+ &self.data
+ }
+}
+
+fn draw_round_corner(
+ rld: &mut RaylibDrawHandle,
+ pos_px: Vec2<f64>,
+ transform: &Transform,
+ selected: bool,
+) {
+ rld.draw_circle_v(
+ pos_px,
+ transform.length_m_to_px(0.05) as f32,
+ if selected {
+ DEFAULT_COLOURS.wall_selected
+ } else {
+ DEFAULT_COLOURS.wall_normal
+ },
+ );
+}
+
+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,
+ if self.selected() {
+ DEFAULT_COLOURS.wall_selected
+ } else {
+ DEFAULT_COLOURS.wall_normal
+ },
+ );
+
+ if self.round_start {
+ draw_round_corner(rld, start_px, transform, self.selected());
+ }
+ if self.round_end {
+ draw_round_corner(rld, end_px, transform, self.selected());
+ }
+ }
+
+ fn set_selected(&mut self, selected: bool) {
+ self.selected = selected;
+ }
+
+ fn selected(&self) -> bool {
+ self.selected
+ }
+
+ 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..50c1cb0 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..5ff3e6a 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 crate::colours::DEFAULT_COLOURS;
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,59 +27,44 @@ 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(
rect_px,
- Color {
- r: 200,
- g: 150,
- b: 150,
- a: 50,
- },
+ DEFAULT_COLOURS.deletion_rect
);
rld.draw_rectangle_lines_ex(
rect_px,
4,
- Color {
- r: 200,
- g: 150,
- b: 150,
- a: 150,
- },
+ DEFAULT_COLOURS.deletion_rect_outline
);
}
}
- 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..aeabf19 100644
--- a/src/tool/mod.rs
+++ b/src/tool/mod.rs
@@ -1,46 +1,63 @@
pub mod deletion_tool;
pub mod icon_tool;
pub mod polygon_room_tool;
-pub mod room_tool;
+pub mod rect_room_tool;
+pub mod selection_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 selection_tool::SelectionTool;
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,
DeletionTool,
+ SelectionTool,
NumTools,
}
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..1b079d2 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 crate::colours::DEFAULT_COLOURS;
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 {
@@ -192,12 +103,7 @@ impl Tool for PolygonRoomTool {
transform.point_m_to_px(&polygon.corners[0]),
transform.point_m_to_px(&polygon.working_corner),
transform.length_m_to_px(0.1) as f32,
- Color {
- r: 150,
- g: 200,
- b: 150,
- a: 255,
- },
+ DEFAULT_COLOURS.room_selected
);
} else if polygon.corners.len() == 2 {
// We have three valid corners, so we can draw a triangle.
@@ -205,12 +111,7 @@ impl Tool for PolygonRoomTool {
transform.point_m_to_px(&polygon.corners[0]),
transform.point_m_to_px(&polygon.corners[1]),
transform.point_m_to_px(&polygon.working_corner),
- Color {
- r: 150,
- g: 200,
- b: 150,
- a: 255,
- },
+ DEFAULT_COLOURS.room_selected
)
} else {
// A proper polygon can be drawn.
@@ -225,12 +126,7 @@ impl Tool for PolygonRoomTool {
transform.point_m_to_px(&triangle[0]),
transform.point_m_to_px(&triangle[1]),
transform.point_m_to_px(&triangle[2]),
- Color {
- r: 150,
- g: 200,
- b: 150,
- a: 255,
- },
+ DEFAULT_COLOURS.room_selected
)
}
} else if polygon.check_validity_completed().is_ok() {
@@ -242,22 +138,49 @@ impl Tool for PolygonRoomTool {
transform.point_m_to_px(&triangle[0]),
transform.point_m_to_px(&triangle[1]),
transform.point_m_to_px(&triangle[2]),
- Color {
- r: 150,
- g: 200,
- b: 150,
- a: 255,
- },
+ DEFAULT_COLOURS.room_selected
)
}
}
}
+ }
+ }
+
+ 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..dfda495
--- /dev/null
+++ b/src/tool/rect_room_tool.rs
@@ -0,0 +1,73 @@
+use super::Tool;
+use crate::map::Map;
+use crate::math::{Rect, Vec2};
+use crate::transform::Transform;
+use crate::colours::DEFAULT_COLOURS;
+use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
+
+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)),
+ DEFAULT_COLOURS.room_selected
+ );
+ }
+ }
+
+ 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/selection_tool.rs b/src/tool/selection_tool.rs
new file mode 100644
index 0000000..49efba9
--- /dev/null
+++ b/src/tool/selection_tool.rs
@@ -0,0 +1,63 @@
+use super::Tool;
+use crate::map::Map;
+use crate::math::{Rect, Surface, Vec2};
+use crate::transform::Transform;
+use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
+use crate::colours::DEFAULT_COLOURS;
+
+pub struct SelectionTool {
+ selection_rect: Option<(Vec2<f64>, Vec2<f64>)>,
+}
+
+impl SelectionTool {
+ pub fn new() -> Self {
+ Self {
+ selection_rect: None,
+ }
+ }
+}
+
+impl Tool for SelectionTool {
+ fn deactivate(&mut self) {
+ self.selection_rect = None;
+ }
+
+ fn update(&mut self, _map: &Map, mouse_pos_m: &Vec2<f64>) {
+ if let Some((_, ref mut pos2)) = &mut self.selection_rect {
+ *pos2 = *mouse_pos_m;
+ }
+ }
+
+ fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
+ if let Some((pos1, pos2)) = self.selection_rect {
+ let rect_px = transform.rect_m_to_px(&Rect::bounding_rect(pos1, pos2));
+ rld.draw_rectangle_rec(
+ rect_px,
+ DEFAULT_COLOURS.selection_rect
+ );
+ rld.draw_rectangle_lines_ex(
+ rect_px,
+ 4,
+ DEFAULT_COLOURS.selection_rect_outline
+ );
+ }
+ }
+
+ fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) {
+ if let Some((pos1, pos2)) = self.selection_rect {
+ // Select all items on the map that are inside of the selection rectangle
+ let bounds = Rect::bounding_rect(pos1, pos2);
+ for element in map.elements_mut() {
+ // TODO: Make it possible to do this additively by custom keybinding.
+ element.set_selected(bounds.contains_rect(&element.bounding_rect()));
+ }
+ self.selection_rect = None;
+ } else {
+ self.selection_rect = Some((*mouse_pos_m, *mouse_pos_m));
+ }
+ }
+
+ fn abort(&mut self) {
+ self.selection_rect = None;
+ }
+}
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;
}
}
diff --git a/uml/class_diagram.uml b/uml/class_diagram.uml
index 91df569..1090679 100644
--- a/uml/class_diagram.uml
+++ b/uml/class_diagram.uml
@@ -144,6 +144,12 @@ package "Tools" #ff5555-ffaaaa {
}
}
+package "Map" #5555ff-aaaaff {
+ entity Map <<struct>> {
+
+ }
+}
+
' Alignment help so all tools are drawn from top to bottom
DeletionTool -d[hidden]- IconTool
IconTool -d[hidden]- PolygonRoomTool