aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--src/button.rs7
-rw-r--r--src/colours.rs40
-rw-r--r--src/config.rs28
-rw-r--r--src/dimension_indicator.rs128
-rw-r--r--src/editor.rs22
-rw-r--r--src/grid.rs27
-rw-r--r--src/gui/dimension_indicator.rs280
-rw-r--r--src/gui/mod.rs11
-rw-r--r--src/gui/tool_sidebar.rs11
-rw-r--r--src/main.rs30
-rw-r--r--src/map/data.rs6
-rw-r--r--src/map/icon.rs8
-rw-r--r--src/map/icon_renderer.rs14
-rw-r--r--src/map/mappable.rs14
-rw-r--r--src/map/mod.rs29
-rw-r--r--src/map/polygon_room.rs34
-rw-r--r--src/map/rect_room.rs23
-rw-r--r--src/map/wall.rs36
-rw-r--r--src/math/line_segment.rs51
-rw-r--r--src/math/mod.rs2
-rw-r--r--src/math/polygon/mod.rs2
-rw-r--r--src/math/polygon/polygon_graph.rs7
-rw-r--r--src/math/rect.rs6
-rw-r--r--src/math/surface.rs2
-rw-r--r--src/math/triangle.rs2
-rw-r--r--src/math/vec2.rs25
-rw-r--r--src/scaleable.rs11
-rw-r--r--src/svg/mod.rs11
-rw-r--r--src/svg/style.rs8
-rw-r--r--src/tool/deletion_tool.rs24
-rw-r--r--src/tool/icon_tool.rs9
-rw-r--r--src/tool/mod.rs23
-rw-r--r--src/tool/polygon_room_tool.rs15
-rw-r--r--src/tool/rect_room_tool.rs12
-rw-r--r--src/tool/selection_tool.rs24
-rw-r--r--src/tool/wall_tool.rs6
-rw-r--r--src/transform.rs8
-rw-r--r--src/transformable.rs17
39 files changed, 723 insertions, 292 deletions
diff --git a/Cargo.toml b/Cargo.toml
index b59cc6a..23c17be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,4 +15,4 @@ svgtypes = "*"
xmltree = "*"
pretty_env_logger = "*"
log = "*"
-thiserror = "*"
+thiserror = "*" \ No newline at end of file
diff --git a/src/button.rs b/src/button.rs
index 89ce9a5..846377e 100644
--- a/src/button.rs
+++ b/src/button.rs
@@ -11,10 +11,13 @@ use std::mem;
/// user has free reign over what key they use for what purpose.
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Button {
+ /// Button on the mouse with internal mouse button representation of raylib.
Mouse(MouseButton),
+ /// Keyboard button with internal keyboard key representation of raylib.
Keyboard(KeyboardKey),
}
+#[allow(missing_docs)]
#[repr(u32)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum MouseButton {
@@ -23,6 +26,7 @@ pub enum MouseButton {
Middle = 2,
}
+#[allow(missing_docs)]
#[repr(u32)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum KeyboardKey {
@@ -134,6 +138,9 @@ pub enum KeyboardKey {
}
impl Button {
+ /// Check if this button is pressed. If `mouse_blocked` is true, mouse buttons are ignored which
+ /// is useful when an element has captured the mouse, but other elements are still queried in the
+ /// background.
pub fn is_pressed(self, rl: &RaylibHandle, mouse_blocked: bool) -> bool {
match self {
Self::Mouse(button) => !mouse_blocked && rl.is_mouse_button_pressed(button.into()),
diff --git a/src/colours.rs b/src/colours.rs
index 8d69869..4a3b799 100644
--- a/src/colours.rs
+++ b/src/colours.rs
@@ -1,18 +1,40 @@
+//! The colour definitions used for items drawn in graf karto.
+
use raylib::ffi::Color;
+/// Contains the default colours used throughout, if nothing else is set.
pub const DEFAULT_COLOURS: Colours = Colours::default();
+/// All the different colours that may be used for different elements of the program. Contains one
+/// entry for each colourable component.
pub struct Colours {
+ /// Colour the rectangle used for the deletion tool is filled with.
pub deletion_rect: Color,
+ /// The colour of the outline of the deletion tool rectangle.
pub deletion_rect_outline: Color,
+ /// The colour that is used for filling the selection tool's rectangle.
pub selection_rect: Color,
+ /// Colour of the selection tool rectangle outline.
pub selection_rect_outline: Color,
+ /// Colour of the rooms that are currently not selected.
pub room_normal: Color,
+ /// The Colour the rooms should be tinted in when they have been selected.
pub room_selected: Color,
+ /// Colour of the walls when they are not selected.
pub wall_normal: Color,
+ /// Colour of the walls when they have been selected.
pub wall_selected: Color,
+ /// Colour of the icons when they are not selected.
pub icon_normal: Color,
+ /// Colour of the icons when they are selected.
pub icon_selected: Color,
+ /// Colour used to draw the rulers (the ruling lines) of the dimension indicator.
+ pub dimension_indicators: Color,
+ /// Colour of the text used to display the size of the dimension indicators dimensions.
+ pub dimension_text: Color,
+ /// The colour used for drawing the lines of the grid which divides the map into chunks of evenly
+ /// spaced cells.
+ pub grid_lines: Color,
}
impl Colours {
@@ -81,6 +103,24 @@ impl Colours {
b: 150,
a: 255,
},
+ dimension_indicators: Color {
+ r: 200,
+ g: 200,
+ b: 200,
+ a: 255,
+ },
+ dimension_text: Color {
+ r: 200,
+ g: 200,
+ b: 200,
+ a: 255,
+ },
+ grid_lines: Color {
+ r: 255,
+ g: 255,
+ b: 255,
+ a: 75,
+ },
}
}
}
diff --git a/src/config.rs b/src/config.rs
index 2a1e5ed..b5abb1e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,3 +1,5 @@
+//! Home of the user configuratable content of graf karto, like keybindings and (TODO) colours etc.
+
use crate::button::*;
use ron::de::from_reader;
use ron::ser::{to_string_pretty, PrettyConfig};
@@ -6,6 +8,8 @@ use std::fs::File;
use std::io::{self, Write};
use std::path::Path;
+/// All configuration parameters the user can set are contained in this struct.
+#[allow(missing_docs)]
#[derive(Deserialize, Serialize)]
pub struct Config {
pub tool_activation_keys: ToolActivationKeys,
@@ -13,7 +17,10 @@ pub struct Config {
pub icon_keys: IconToolKeys,
}
+#[allow(missing_docs)]
#[derive(Deserialize, Serialize)]
+/// The keys used to activate the individual tools. These keystrokes will not be sent to the tools,
+/// but instead will be handled by the editor where the tools are registered.
pub struct ToolActivationKeys {
pub deletion: Button,
pub icon: Button,
@@ -24,21 +31,38 @@ pub struct ToolActivationKeys {
}
#[derive(Deserialize, Serialize)]
+/// Keys that are useful to most tools. These are packaged so that not every tool has the same n keys
+/// and then some more.
pub struct ToolGeneralKeys {
+ /// Keybinding to, where applicable, place a single node (usually a vertex) for the tool in
+ /// question.
pub place_single: Button,
+ /// Finish up whatever one is doing with the current tool, without removing information.
pub finish: Button,
+ /// Abort whatever one is doing with the current tool which means the last atomic action will not
+ /// be pushed into the map items.
pub abort: Button,
}
#[derive(Clone, Serialize, Deserialize)]
+/// Key bindings that are individually interesting to the icon tool.
pub struct IconToolKeys {
+ /// Key to change to the next icon of the icon list.
pub next: Button,
+ /// Key to change to the previous icon of the icon list.
pub previous: Button,
+ /// Rotate the working icon clockwise by a certain amount (currently 45 degrees)
pub rotate_clockwise: Button,
+ /// Rotate the working icon counterclockwise by a certain amount (currently 45 degrees)
pub rotate_counterclockwise: Button,
}
impl Config {
+ /// Try to parse a configuration from the file located at path.
+ ///
+ /// # Errors
+ /// If the file is not found or can not be read or parsed for a different reason, an IO-Error is
+ /// returned.
pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Config> {
let file = File::open(&path)?;
match from_reader(file) {
@@ -47,6 +71,10 @@ impl Config {
}
}
+ /// Try to write the configuration to the file at path. If the file exists, it will be overwritten.
+ ///
+ /// # Errors
+ /// If the file can not be written, for example for lack of permissions, an IO-Error is returned.
pub fn write_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let mut file = File::create(&path)?;
diff --git a/src/dimension_indicator.rs b/src/dimension_indicator.rs
deleted file mode 100644
index cc12f0b..0000000
--- a/src/dimension_indicator.rs
+++ /dev/null
@@ -1,128 +0,0 @@
-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/editor.rs b/src/editor.rs
index e6a2dcb..d541fb6 100644
--- a/src/editor.rs
+++ b/src/editor.rs
@@ -1,3 +1,10 @@
+//! Element creation base
+//!
+//! The actual editor environment sits here. This especially means all tools that require low-level
+//! access to the data of items currently being created. While this may be subject to change, there is
+//! currently a difference between things that are being created (inside the editor) and things that
+//! are part of the environment (the map).
+
use crate::button::{Button, MouseButton};
use crate::config::Config;
use crate::grid::{snap_to_grid, SNAP_SIZE};
@@ -8,6 +15,7 @@ use raylib::core::drawing::RaylibDrawHandle;
use raylib::{RaylibHandle, RaylibThread};
use std::collections::HashMap;
+/// The editor containing all tools and currently the map of the stuff that has been created.
pub struct Editor {
map: Map,
/// HashMap that matches the ToolType with its proper activation key and of course the tool
@@ -18,6 +26,8 @@ pub struct Editor {
}
impl Editor {
+ /// Create a new editor with all tools necessary. There should be only one editor per program
+ /// instance.
pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread, config: Config) -> Self {
let map = Map::new(rl, rlt);
@@ -74,7 +84,8 @@ impl Editor {
}
}
- /// Get the currently active tool.
+ /// Get the currently active tool. Since the every tool exists only once, it is entirely indexable
+ /// by its type, which is what is actually returned.
pub fn active(&self) -> ToolType {
self.active
}
@@ -93,6 +104,8 @@ impl Editor {
}
}
+ /// Update the internal editor data where necessary and handle selecting different tools, aswell
+ /// as updating the currently active tool. Should be called once every frame.
pub fn update(&mut self, rl: &mut RaylibHandle, transform: &Transform, mouse_blocked: bool) {
// Handle keybindings for tool change
for (&tool_type, (_, activation_key)) in self.tools.iter() {
@@ -175,13 +188,20 @@ impl Editor {
}
}
+ /// Draw all tools and in case of the active tool also what is currently being edited by it, if
+ /// that exists.
pub fn draw_tools(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
for (tool, _) in self.tools.values() {
tool.draw(rld, transform);
}
}
+ /// Get the world containing all finished elements.
pub fn map(&self) -> &Map {
&self.map
}
+ /// Get the world containing all finished elements mutably.
+ pub fn map_mut(&mut self) -> &mut Map {
+ &mut self.map
+ }
}
diff --git a/src/grid.rs b/src/grid.rs
index ec27fa7..d1c4b15 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -1,18 +1,13 @@
+//! The grid used to divide the map into evenly sized chunks.
+
+use crate::colours::DEFAULT_COLOURS;
use crate::math::{self, Vec2};
use crate::transform::Transform;
use raylib::drawing::RaylibDraw;
-use raylib::ffi::Color;
/// The internal grid length which will be used to snap things to it.
pub const SNAP_SIZE: f64 = 0.5;
-pub const LINE_COLOUR: Color = Color {
- r: 255,
- g: 255,
- b: 255,
- a: 75,
-};
-
/// Snap a vector to the grid with the factor being the sub-grid accuracy. For instance, 0.5 will
/// snap to half a grid cell, while 2.0 would snap to every second grid cell
pub fn snap_to_grid(mut vec: Vec2<f64>, snap_fraction: f64) -> Vec2<f64> {
@@ -38,7 +33,13 @@ where
let mut draw_y = transform.point_m_to_px(&cell).y;
loop {
draw_y = math::round(draw_y, 1.);
- rld.draw_line(0, draw_y as i32, screen_width, draw_y as i32, LINE_COLOUR);
+ rld.draw_line(
+ 0,
+ draw_y as i32,
+ screen_width,
+ draw_y as i32,
+ DEFAULT_COLOURS.grid_lines,
+ );
cell.y += 1.;
draw_y = transform.point_m_to_px(&cell).y;
@@ -50,7 +51,13 @@ where
let mut draw_x = transform.point_m_to_px(&cell).x;
loop {
draw_x = math::round(draw_x, 1.);
- rld.draw_line(draw_x as i32, 0, draw_x as i32, screen_height, LINE_COLOUR);
+ rld.draw_line(
+ draw_x as i32,
+ 0,
+ draw_x as i32,
+ screen_height,
+ DEFAULT_COLOURS.grid_lines,
+ );
cell.x += 1.;
draw_x = transform.point_m_to_px(&cell).x;
diff --git a/src/gui/dimension_indicator.rs b/src/gui/dimension_indicator.rs
index cc12f0b..e8848fe 100644
--- a/src/gui/dimension_indicator.rs
+++ b/src/gui/dimension_indicator.rs
@@ -1,74 +1,250 @@
-use crate::math::Vec2;
+//! An interface element that shows the size of the selected map items and provides a means to
+//! manually change the size of them in a precise manner should need be.
+
+use crate::colours::DEFAULT_COLOURS;
+use crate::map::Map;
+use crate::math::{self, Rect, Vec2};
use crate::transform::Transform;
+use nalgebra::{Matrix3, Vector2};
use raylib::drawing::RaylibDraw;
-use raylib::ffi::Color;
+use raylib::ffi::KeyboardKey;
+use raylib::RaylibHandle;
+
+/// A state the [DimensionIndicator] is currently in. This determines the behaviour of it and what
+/// inputs it might be waiting for.
+enum State {
+ /// In this state, the indicator is not trying to read any keyboard input, but will instead watch
+ /// for any changes to the dimensions from a different source, updating its display.
+ Watching,
+ /// In this state, the indicator will capture keyboard input and attempt to set the dimensions
+ /// according to whatever was entered. If the dimensions cannot be set, the indicator will use
+ /// the last valid dimensions.
+ Ruling {
+ dim_x: String,
+ dim_y: String,
+ editing_x: bool,
+ },
+}
+/// Used to render the horizontal and vertical dimensions of whatever is selected on the map and, if
+/// the user so desires edit them directly by entering values into it.
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>)>,
+ /// The [State] the dimension indicator is currently in.
+ state: State,
+ /// The last dimensions that were valid.
+ bounds: Rect<f64>,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::Watching
+ }
}
impl DimensionIndicator {
- #[allow(clippy::new_without_default)]
+ /// Create a new dimension indicator. While it is possible to have multiple instances, this is
+ /// not generally recommended, since they will need to be managed carefully or otherwise steal
+ /// keystrokes from each other.
pub fn new() -> Self {
Self {
- length_lines: Vec::new(),
+ state: State::default(),
+ bounds: Rect::new(0., 0., 0., 0.),
}
}
- pub fn from_corner_points(corner_points: &[Vec2<f64>]) -> Self {
- let mut this = Self::new();
- this.update_dimensions(corner_points);
-
- this
+ /// Update whatever is selected on the map according to the dimension indicator rules and rulers.
+ pub fn update(&mut self, map: &mut Map, rl: &mut RaylibHandle) {
+ match &self.state {
+ &State::Watching => self.update_watching(map, rl),
+ &State::Ruling { .. } => self.update_ruling(map, rl),
+ };
}
- pub fn clear_dimensions(&mut self) {
- self.length_lines.clear();
+ fn update_watching(&mut self, map: &Map, rl: &RaylibHandle) {
+ let mut min: Vec2<f64> = Vec2::default();
+ let mut max: Vec2<f64> = Vec2::default();
+
+ /* Try to find selected items. If no items exist, the dimension indicator is set to its
+ * default, otherwise it is adjusted to the size of the combined selection.
+ */
+ let mut selection_exists = false;
+ for e in map.elements() {
+ if e.selected() {
+ let element_bounds = e.bounding_rect();
+ if selection_exists {
+ // Adjust the currently detected selection size.
+ min.x = math::partial_min(min.x, element_bounds.x);
+ min.y = math::partial_min(min.y, element_bounds.y);
+ max.x = math::partial_max(max.x, element_bounds.x + element_bounds.w);
+ max.y = math::partial_max(max.y, element_bounds.y + element_bounds.h);
+ } else {
+ // No selection size detected yet. Set now.
+ min.x = element_bounds.x;
+ min.y = element_bounds.y;
+ max.x = element_bounds.x + element_bounds.w;
+ max.y = element_bounds.y + element_bounds.h;
+ }
+ selection_exists = true;
+ }
+ }
+
+ // Set the current selection limits, if any.
+ self.bounds = if selection_exists {
+ Rect::bounding_rect(min, max)
+ } else {
+ Rect::new(0., 0., 0., 0.)
+ };
+
+ // Check if the user wants to change into editing mode, which the user can only do if there
+ // is a selection to begin with.
+ if selection_exists && rl.is_key_pressed(KeyboardKey::KEY_TAB) {
+ self.state = State::Ruling {
+ dim_x: self.bounds.w.to_string(),
+ dim_y: self.bounds.h.to_string(),
+ editing_x: true,
+ };
+ }
}
- /// 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.");
+ fn update_ruling(&mut self, map: &mut Map, rl: &mut RaylibHandle) {
+ // Get the currently edited dimension for processing.
+ let (edited_dim, editing_x) = match &mut self.state {
+ State::Watching => panic!("Called ruler update when in watching state"),
+ State::Ruling {
+ dim_x,
+ dim_y,
+ editing_x,
+ } => {
+ if *editing_x {
+ (dim_x, editing_x)
+ } else {
+ (dim_y, editing_x)
+ }
+ }
+ };
+
+ // Switch the currently edited dimension when pressing tab.
+ if rl.is_key_pressed(KeyboardKey::KEY_TAB) {
+ *editing_x = !*editing_x;
+ return;
+ }
+ // Finish editing mode on enter.
+ if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
+ self.state = State::Watching;
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;
- }
+ // Marker to see if the dimensions will have to be checked for an update.
+ let mut dimension_changed = false;
+ // Delete the last character of the dimension on backspace.
+ if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) {
+ edited_dim.pop();
+ dimension_changed = true;
+ }
+ /* Capture the current key and try to add it to the string of the current dimension,
+ * if possible.
+ */
+ else if let Some(key) = rl.get_key_pressed() {
+ match key {
+ // Add a decimal point to the dimension if possible.
+ KeyboardKey::KEY_PERIOD => {
+ if !edited_dim.contains('.') {
+ edited_dim.push('.');
+ }
+ // Nothing changed here, since there is an implicit .0 at the end.
+ }
+ // Handle the entered key if it is a number to append it to the currently edited dimension.
+ _ => {
+ if key as u16 >= KeyboardKey::KEY_ZERO as u16
+ && key as u16 <= KeyboardKey::KEY_NINE as u16
+ {
+ edited_dim.push(key as u8 as char);
+ dimension_changed = true;
+ }
+ }
+ };
+ }
+
+ if dimension_changed {
+ /* Try to parse the dimension from the currently edited string. If it
+ * is valid, change the dimensions of the currently selected items. If
+ * not, ignore the change and wait for a valid dimension.
+ */
+ if let Ok(dim) = edited_dim.parse::<f64>() {
+ let new_bounds = if *editing_x {
+ Rect::new(self.bounds.x, self.bounds.y, dim, self.bounds.h)
+ } else {
+ Rect::new(self.bounds.x, self.bounds.y, self.bounds.h, dim)
+ };
- if point.y < min.y {
- min.y = point.y;
+ self.set_bounds(map, new_bounds);
}
- if point.y > max.y {
- max.y = point.y;
+ }
+ }
+
+ /// Set the selection boundaries to the given bounds. Tries to transform the
+ /// currently selected items in the map so they fit inside of the new bounding box.
+ ///
+ /// # Panics
+ /// If the `bounds` have a negative value for width or height, the dimensions
+ /// cannot be set and the function will panic.
+ pub fn set_bounds(&mut self, map: &mut Map, bounds: Rect<f64>) {
+ if bounds.w <= 0. || bounds.h <= 0. {
+ panic!("Cannot set dimensions of elements to zero.");
+ }
+
+ // If the bounds are the same as before, there is nothing to do.
+ if self.bounds == bounds {
+ return;
+ }
+
+ /* Create a matrix to transform from the current rectangle bounds into the
+ * new bounds. Internally, this is a three-step process. First, we
+ * translate the points currently in the bounding box to the origin
+ * (0, 0) origin vector of the map, then scale and finally move it to the
+ * origin of the new rectangle. This needs to be applied to all vertices
+ * of all elements that can be scaled.
+ */
+ let scale = Vector2::new(bounds.w / self.bounds.w, bounds.h / self.bounds.h);
+ let transform = Matrix3::new_translation(&Vector2::new(-self.bounds.x, -self.bounds.y))
+ .append_nonuniform_scaling(&scale)
+ .append_translation(&Vector2::new(bounds.x, bounds.y));
+
+ for element in map.elements_mut() {
+ if element.selected() {
+ if let Some(transformable) = element.as_non_rigid_mut() {
+ transformable.apply_matrix(&transform);
+ }
}
}
- // 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)));
+ self.bounds = bounds;
}
+ /// Draw the dimensions detected on the current selection.
pub fn draw(&self, rld: &mut impl RaylibDraw, transform: &Transform) {
- // Draw all the dimension lines.
- for (start, end) in &self.length_lines {
+ /* Ignore a selection that has no non-null dimensions, since this usually
+ * indicates that there is nothing to be scaled.
+ */
+ if self.bounds.w == 0. && self.bounds.h == 0. {
+ return;
+ }
+
+ let (dim_str_width, dim_str_height) = match &self.state {
+ State::Watching => (self.bounds.w.to_string(), self.bounds.h.to_string()),
+ State::Ruling { dim_x, dim_y, .. } => (dim_x.clone(), dim_y.clone()),
+ };
+
+ // Draw the horizontal dimension at the bottom and the vertical dimension to the right.
+ // Use the valid dimensions, but show the edited dimensions in the String (should they differ)
+ let top_right = Vec2::new(self.bounds.x + self.bounds.w, self.bounds.y);
+ let bot_left = Vec2::new(self.bounds.x, self.bounds.y + self.bounds.h);
+ let bot_right = Vec2::new(self.bounds.x + self.bounds.w, self.bounds.y + self.bounds.h);
+ let dimensions = [
+ (bot_left, bot_right, dim_str_width),
+ (bot_right, top_right, dim_str_height),
+ ];
+ for (start, end, dim_str) in &dimensions {
// Don't draw anything if the length is zero.
if start == end {
continue;
@@ -89,27 +265,21 @@ impl DimensionIndicator {
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,
+ DEFAULT_COLOURS.dimension_indicators,
);
rld.draw_line_ex(
end_px - line_normal * 5.,
end_px + line_normal * 5.,
2.,
- line_colour,
+ DEFAULT_COLOURS.dimension_indicators,
);
// Then the actual indicator line.
- rld.draw_line_ex(start_px, end_px, 2., line_colour);
+ rld.draw_line_ex(start_px, end_px, 2., DEFAULT_COLOURS.dimension_indicators);
/* 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
@@ -117,11 +287,11 @@ impl DimensionIndicator {
*/
let text_pos = transform.point_m_to_px(&((*end + *start) / 2.)) + line_normal * 20.;
rld.draw_text(
- &format!("{}m", &(*end - *start).length()),
+ &format!("{}m", dim_str),
text_pos.x as i32,
text_pos.y as i32,
20,
- line_colour,
+ DEFAULT_COLOURS.dimension_text,
);
}
}
diff --git a/src/gui/mod.rs b/src/gui/mod.rs
index a4a000b..a94122e 100644
--- a/src/gui/mod.rs
+++ b/src/gui/mod.rs
@@ -1,3 +1,14 @@
+//! General graphical user interfaces
+//!
+//! This mod does not contain all graphical content on screen, but all user interfaces that is drawn
+//! that is not contained in a different category. This means all interface elements where it does not
+//! make sense to bind it to any other part of the program, for instance a tool or type of element.
+//! It also does *not* contain anything that does anything that is not triggered by the user, which
+//! means everything is called top-down from this module. A function in this module should not be
+//! called from any point in the program except the main loop, where the user input is polled.
+
+pub mod dimension_indicator;
pub mod tool_sidebar;
+pub use self::dimension_indicator::*;
pub use self::tool_sidebar::*;
diff --git a/src/gui/tool_sidebar.rs b/src/gui/tool_sidebar.rs
index 7674c47..e6b8867 100644
--- a/src/gui/tool_sidebar.rs
+++ b/src/gui/tool_sidebar.rs
@@ -1,3 +1,8 @@
+//! The sidebar showing all tools available to the user. This toolbar handles changing the active tool
+//! based on the mouse input and (TODO!) keyboard inputs.
+// TODO: Currently, the keyboard shortcuts for tools are handled by the editor, but a lot speaks for
+// them being handled by the ToolSidebar instead.
+
use crate::math::{Rect, Surface, Vec2};
use crate::tool::ToolType;
use crate::Editor;
@@ -6,13 +11,16 @@ use raylib::rgui::RaylibDrawGui;
use raylib::{RaylibHandle, RaylibThread};
use std::mem;
+/// The file containing textures for all buttons describing the tools.
pub const BUTTON_FILE: &str = "assets/button/tool_buttons.png";
+/// Sidebar that renders and handles input for the tool activation buttons.
pub struct ToolSidebar {
button_texture: Texture2D,
}
impl ToolSidebar {
+ /// Create a new tool sidebar. There should be only one sidebar per program instance.
pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self {
let button_texture = rl
.load_texture(rlt, BUTTON_FILE)
@@ -31,6 +39,9 @@ impl ToolSidebar {
Self::panel_rect(screen_height).contains_point(&mouse_pos)
}
+ /// Draw the tool buttons and encasing panel. Because of the way raylib works, this also handles
+ /// clicking on tool buttons, which may be changed in the future, should a different gui be
+ /// chosen.
pub fn draw(&self, screen_height: u16, rld: &mut impl RaylibDrawGui, editor: &mut Editor) {
rld.gui_panel(Self::panel_rect(screen_height));
diff --git a/src/main.rs b/src/main.rs
index 8ddc587..8c1d63e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,22 @@
+//! # Graf Karto cartographer
+//!
+//! ### What is it exactly
+//! Graf Karto is a table top role playing game (TTRPG) map creation tool that is optimised for real
+//! time map interaction.
+//!
+//! ### Motivation
+//! While there are certainly many TTRPG map creation tools for single user and multi user available
+//! online and on the market, we felt that most of them lack features or are to unwieldy to seriously
+//! consider for real time dungeon drawing, say for instance when drawing a map for an old school
+//! revival style game. This is why Graf Karto is optimised for speed above pretty graphical features.
+//! The aim is for the user not to have to think much about how they are going to achieve what they are
+//! doing and how they are going to make it pretty, but should just be able to *do* it, to have time to
+//! worry about other things. This does not mean that all maps created should visually be equivalent to
+//! a steaming pile of hot garbage, but if the visuals should try to get in the way, usability and speed
+//! takes precedence.
+
#![allow(dead_code)]
+#![warn(missing_docs)]
#[macro_use]
extern crate log;
@@ -6,26 +24,27 @@ 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;
pub mod math;
-pub mod scaleable;
pub mod svg;
pub mod tool;
pub mod transform;
+pub mod transformable;
use config::Config;
use editor::Editor;
-use gui::ToolSidebar;
+use gui::{DimensionIndicator, ToolSidebar};
use raylib::prelude::*;
use std::ffi::CString;
use std::io;
use transform::Transform;
+/// Location of the file containing the style used for the raylib user interface.
pub const GUI_STYLE: &str = "assets/style/cyber.rgs";
+/// Location of the graf karto configuration options file.
pub const CONFIG_FILE: &str = "config.ron";
fn main() {
@@ -65,6 +84,7 @@ fn main() {
));
let mut editor = Editor::new(&mut rl, &thread, config);
+ let mut dimension_indicator = DimensionIndicator::new();
let tool_sidebar = ToolSidebar::new(&mut rl, &thread);
let mut transform = Transform::new();
@@ -90,6 +110,8 @@ fn main() {
);
}
+ dimension_indicator.update(editor.map_mut(), &mut rl);
+
editor.update(
&mut rl,
&transform,
@@ -105,6 +127,8 @@ fn main() {
editor.draw_tools(&mut d, &transform);
tool_sidebar.draw(screen_height as u16, &mut d, &mut editor);
+
+ dimension_indicator.draw(&mut d, &transform);
}
}
}
diff --git a/src/map/data.rs b/src/map/data.rs
index f978081..1031d3c 100644
--- a/src/map/data.rs
+++ b/src/map/data.rs
@@ -1,3 +1,5 @@
+//! Module containing the raw map data version of the map.
+
use super::{IconData, PolygonRoomData, RectRoomData, WallData};
use ron::de::from_reader;
use ron::ser::{to_string_pretty, PrettyConfig};
@@ -18,6 +20,7 @@ pub struct MapData {
}
impl MapData {
+ /// Create a serialisable map data type from the data elements contained in a map.
pub fn new(
rect_rooms: Vec<RectRoomData>,
polygon_rooms: Vec<PolygonRoomData>,
@@ -32,6 +35,7 @@ impl MapData {
}
}
+ /// Load the map data from a file. Fails if the file does not exist or cannot be correctly parsed.
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) {
@@ -44,6 +48,8 @@ impl MapData {
Ok(data)
}
+ /// Write the map data to the file located at `path`. If the file already exists, it will be
+ /// overwritten. If the write fails, an IO-Error is returned.
pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let mut file = File::create(&path)?;
diff --git a/src/map/icon.rs b/src/map/icon.rs
index f623c98..2e45486 100644
--- a/src/map/icon.rs
+++ b/src/map/icon.rs
@@ -1,3 +1,6 @@
+//! Icons are map elements that have a specific size and cannot be stretched. They are usually used
+//! as markers for specific places in the world.
+
use super::icon_renderer::IconRenderer;
use crate::colours::DEFAULT_COLOURS;
use crate::map::Mappable;
@@ -8,6 +11,7 @@ use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
+/// The icon data necessary to create an Icon again.
#[derive(Clone, Serialize, Deserialize)]
pub struct IconData {
/// The id of the icon is the icons position in the currently loaded icon_data vector.
@@ -18,6 +22,7 @@ pub struct IconData {
pub rotation: f64,
}
+/// Describes an icon in the world and can be drawn.
#[derive(Clone)]
pub struct Icon {
data: IconData,
@@ -26,6 +31,8 @@ pub struct Icon {
}
impl Icon {
+ /// Create a new icon. Requires the icon renderer that is used to render this icon, as well as all
+ /// the information necessary to describe the icon itself.
pub fn new(id: usize, position: Vec2<f64>, rotation: f64, renderer: Rc<IconRenderer>) -> Self {
Self::from_data(
IconData {
@@ -37,6 +44,7 @@ impl Icon {
)
}
+ /// Like `new()`, but with the icon data bundled into the icon data type.
pub fn from_data(data: IconData, renderer: Rc<IconRenderer>) -> Self {
Self {
data,
diff --git a/src/map/icon_renderer.rs b/src/map/icon_renderer.rs
index fb81e24..eef053d 100644
--- a/src/map/icon_renderer.rs
+++ b/src/map/icon_renderer.rs
@@ -1,3 +1,6 @@
+//! Since the same icon may be used very often on a map, it becomes impracticalto let every icon have
+//! it's own texture data saved, which is why a texture manager type of struct is used for it instead.
+
use crate::math::Vec2;
use raylib::core::texture::Texture2D;
use raylib::{RaylibHandle, RaylibThread};
@@ -5,6 +8,7 @@ use ron::de::from_reader;
use serde::Deserialize;
use std::fs::{self, File};
+/// The directory containing all files related to icons.
pub const ICON_DIR: &str = "assets/icons";
#[derive(Deserialize)]
@@ -16,11 +20,15 @@ pub(super) struct IconFileInfo {
pub pixels_per_m: f64,
}
+/// Manager for all icon texture or rendering data.
pub struct IconRenderer {
texture_data: Vec<(Texture2D, IconFileInfo)>,
}
impl IconRenderer {
+ /// Create a new icon renderer. This loads all textures and information for icons that is needed
+ /// to draw them to the screen. Usually, there should be only one IconRenderer, or at least one
+ /// renderer per directory and program instance.
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
@@ -67,10 +75,16 @@ impl IconRenderer {
Self { texture_data }
}
+ /// Get the data needed to render an icon of type `icon_id`.
+ ///
+ /// # Panics
+ /// If the `icon_id` does not describe a valid icon (is out of bounds), there is no data to be
+ /// accessed and the function panics.
pub(super) fn get(&self, icon_id: usize) -> &(Texture2D, IconFileInfo) {
&self.texture_data[icon_id]
}
+ /// The number of icons registered in this icon-renderer.
pub fn num_icons(&self) -> usize {
self.texture_data.len()
}
diff --git a/src/map/mappable.rs b/src/map/mappable.rs
index b348c4b..7978f50 100644
--- a/src/map/mappable.rs
+++ b/src/map/mappable.rs
@@ -1,11 +1,12 @@
//! 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
+//! dimension on the map and may be transformable in various ways.
use crate::math::Rect;
-use crate::scaleable::Scaleable;
use crate::transform::Transform;
+use crate::transformable::NonRigidTransformable;
use raylib::drawing::RaylibDrawHandle;
+/// Anything that can be added to the map or world must implement this trait.
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.
@@ -21,7 +22,14 @@ pub trait Mappable {
/// 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> {
+ /// If this mappable item can be transformed in a non-rigid way, a dynamic reference is returned,
+ /// otherwise none.
+ fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
+ None
+ }
+
+ /// The same as `as_non_rigid`, but mutably.
+ fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
None
}
}
diff --git a/src/map/mod.rs b/src/map/mod.rs
index ff03474..88a7e6c 100644
--- a/src/map/mod.rs
+++ b/src/map/mod.rs
@@ -1,3 +1,16 @@
+//! The map contains all the items that make up the world.
+//!
+//! There are two main structs to look out for, the first being [Map]. This is the interaction point
+//! for most parts of the program. It contains the actual elements that are drawn on the screen. and
+//! can be changed by the user.
+//! The second is [MapData] and it contains the data that can be saved/loaded and distributed. Every
+//! map item has an internal item that it can be dereferenced to and can be used to construct this
+//! exact item in the same world elsewhere or at a different time. This is often different from the
+//! item that is being drawn. An example would be the [PolygonRoom], which contains a triangulated
+//! version of itself, so it can be drawn without always having to compute the triangles every frame.
+//! It's data type however [PolygonRoomData] contains only the raw polygon data, not the triangulated
+//! version, since that is enough to create the same [PolygonRoom] again.
+
pub mod data;
pub mod icon;
pub mod icon_renderer;
@@ -19,6 +32,7 @@ use raylib::drawing::RaylibDrawHandle;
use raylib::{RaylibHandle, RaylibThread};
use std::rc::Rc;
+/// The map containing all map elements that are seen on the screen.
pub struct Map {
rect_rooms: Vec<RectRoom>,
polygon_rooms: Vec<PolygonRoom>,
@@ -28,6 +42,7 @@ pub struct Map {
}
impl Map {
+ /// Create a new, empty map/world.
pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self {
Self {
rect_rooms: Vec::new(),
@@ -38,14 +53,20 @@ impl Map {
}
}
+ /// Add a rectangularly shaped room to the world. Since the polygon room tool was added, and
+ /// afaik rects are polygon rooms, this will be phased out in favour of only having polygon
+ /// rooms, which will then become just normal rooms.
pub fn push_rect_room(&mut self, room_data: RectRoomData) {
self.rect_rooms.push(RectRoom::from_data(room_data));
}
+ /// Add a room to the map. Currently, holes are not supported in the polygon, but this might
+ /// change later.
pub fn push_polygon_room(&mut self, room_data: PolygonRoomData) {
self.polygon_rooms.push(PolygonRoom::from_data(room_data));
}
+ /// Add a wall to the world.
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.
@@ -70,21 +91,25 @@ impl Map {
.push(Wall::from_data(wall_data, start_intersects, end_intersects));
}
+ /// Add an icon to the world.
pub fn push_icon(&mut self, icon: Icon) {
self.icons.push(icon);
}
+ /// Draw all elements of the map to the screen. This should be called after the background of the
+ /// map has already been drawn.
pub fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
for element in self.elements() {
element.draw(rld, transform);
}
}
+ /// Get the icon-renderer that is currently used to render the icons.
pub fn icon_renderer(&self) -> Rc<IconRenderer> {
self.icon_renderer.clone()
}
- /// Retain all map elements that fulfill the given predicate.
+ /// Retain all map elements that fulfill the given predicate, removing everything else.
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&dyn Mappable) -> bool,
@@ -107,6 +132,8 @@ impl Map {
.chain(self.icons.iter().map(|i| i as &dyn Mappable))
}
+ /// Iterator over all elements, but the individual elements can be mutated. It is however
+ /// impossible to add or remove elements in this way. For that, use the dedicated functions.
pub fn elements_mut(&mut self) -> impl Iterator<Item = &mut dyn Mappable> {
self.rect_rooms
.iter_mut()
diff --git a/src/map/polygon_room.rs b/src/map/polygon_room.rs
index a209a7c..fd4122e 100644
--- a/src/map/polygon_room.rs
+++ b/src/map/polygon_room.rs
@@ -1,12 +1,18 @@
+//! Polygon rooms are the standard rooms in graf karto. They can take the form of anything that a
+//! [Polygon](crate::math::Polygon) can have.
+
use super::Mappable;
use crate::colours::DEFAULT_COLOURS;
-use crate::math::{self, Polygon, Rect, Triangle, Vec2};
-use crate::scaleable::Scaleable;
+use crate::math::{self, Polygon, Rect, Triangle};
use crate::transform::Transform;
+use crate::transformable::NonRigidTransformable;
+use nalgebra::{Matrix3, Point2};
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
+/// Data type for the Polygon room.
pub type PolygonRoomData = Polygon<f64>;
+/// A polygon room, which can be placed and modified in the world.
pub struct PolygonRoom {
data: PolygonRoomData,
// The polygon shape, but in triangles, so the polygon does not have to be triangulated every frame.
@@ -15,6 +21,7 @@ pub struct PolygonRoom {
}
impl PolygonRoom {
+ /// Create a room from the given polygon data.
pub fn from_data(data: PolygonRoomData) -> Self {
Self {
data: data.clone(),
@@ -23,6 +30,8 @@ impl PolygonRoom {
}
}
+ // When the internal polygon changes, it must be retriangulated to be drawn on the screen
+ // properly, so this function must be called any time that happens.
fn retriangulate(&mut self) {
self.triangulated = math::triangulate(self.data.clone());
}
@@ -56,20 +65,21 @@ impl Mappable for PolygonRoom {
Rect::bounding_rect_n(&self.data.corners())
}
- fn as_scaleable(&self) -> Option<&dyn Scaleable> {
- Some(self as &dyn Scaleable)
+ fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
+ Some(self as &dyn NonRigidTransformable)
}
-}
-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");
- }
+ fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
+ Some(self as &mut dyn NonRigidTransformable)
+ }
+}
+impl NonRigidTransformable for PolygonRoom {
+ fn apply_matrix(&mut self, matrix: &Matrix3<f64>) {
for corner in self.data.corners_mut() {
- corner.x *= by.x;
- corner.y *= by.y;
+ *corner = matrix
+ .transform_point(&Point2::new(corner.x, corner.y))
+ .into();
}
self.retriangulate();
diff --git a/src/map/rect_room.rs b/src/map/rect_room.rs
index 5008c63..6ed3ed6 100644
--- a/src/map/rect_room.rs
+++ b/src/map/rect_room.rs
@@ -1,14 +1,17 @@
+//! Deprecated simple rectangular room type.
+
use crate::colours::DEFAULT_COLOURS;
use crate::map::Mappable;
-use crate::math::{Rect, Vec2};
-use crate::scaleable::Scaleable;
+use crate::math::Rect;
use crate::transform::Transform;
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
use serde::Serialize;
use std::ops::{Deref, DerefMut};
+#[allow(missing_docs)]
pub type RectRoomData = Rect<f64>;
+#[allow(missing_docs)]
#[derive(Serialize)]
pub struct RectRoom {
data: RectRoomData,
@@ -16,6 +19,7 @@ pub struct RectRoom {
}
impl RectRoom {
+ #[allow(missing_docs)]
pub fn from_data(data: RectRoomData) -> Self {
RectRoom {
data,
@@ -47,21 +51,6 @@ impl Mappable for RectRoom {
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 {
diff --git a/src/map/wall.rs b/src/map/wall.rs
index 22393bb..f1748bb 100644
--- a/src/map/wall.rs
+++ b/src/map/wall.rs
@@ -1,13 +1,23 @@
+//! Walls, solid barriers that are generally unscaleable.
+//!
+//! This interpretation is generally up to the GM to decide, but generally speaking, a wall cannot be
+//! crossed by a player. If special conditions apply (for instance, when the player wants to scale the
+//! wall), a check is necessary. If a check is not necessary, then maybe you were not thinking about
+//! a wall.
+
use super::Mappable;
use crate::colours::DEFAULT_COLOURS;
use crate::math::{LineSegment, Rect, Vec2};
-use crate::scaleable::Scaleable;
use crate::transform::Transform;
+use crate::transformable::NonRigidTransformable;
+use nalgebra::Matrix3;
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
use std::ops::{Deref, DerefMut};
+/// The data which defines a wall segment completely for serialisation.
pub type WallData = LineSegment<f64>;
+/// A solid wall a player cannot go through, except if special conditions apply.
pub struct Wall {
data: WallData,
selected: bool,
@@ -16,6 +26,7 @@ pub struct Wall {
}
impl Wall {
+ /// Create a new wall from the deserialised data and information known from internal sources.
pub fn from_data(data: WallData, round_start: bool, round_end: bool) -> Self {
Self {
data,
@@ -25,6 +36,7 @@ impl Wall {
}
}
+ /// Get the internal data for serialisation
pub fn data(&self) -> &WallData {
&self.data
}
@@ -81,18 +93,20 @@ impl Mappable for Wall {
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");
- }
+ fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
+ Some(self as &dyn NonRigidTransformable)
+ }
+
+ fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
+ Some(self as &mut dyn NonRigidTransformable)
+ }
+}
- 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 NonRigidTransformable for Wall {
+ fn apply_matrix(&mut self, matrix: &Matrix3<f64>) {
+ self.data.start = matrix.transform_point(&self.data.start.into()).into();
+ self.data.end = matrix.transform_point(&self.data.end.into()).into();
}
}
diff --git a/src/math/line_segment.rs b/src/math/line_segment.rs
index b496787..204cf0c 100644
--- a/src/math/line_segment.rs
+++ b/src/math/line_segment.rs
@@ -1,3 +1,6 @@
+//! A line segment is like a line, but with a start and an end, with the line only being between
+//! those two.
+
use super::{Rect, Surface, TripletOrientation, Vec2};
use alga::general::{ClosedDiv, ClosedMul, ClosedSub};
use nalgebra::{RealField, Scalar};
@@ -5,6 +8,7 @@ use num_traits::Zero;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
+#[allow(missing_docs)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LineSegment<T: Scalar + Copy> {
pub start: Vec2<T>,
@@ -12,6 +16,7 @@ pub struct LineSegment<T: Scalar + Copy> {
}
impl<T: Scalar + Copy> LineSegment<T> {
+ /// Create a new line segment from `start` to `end`
pub fn new(start: Vec2<T>, end: Vec2<T>) -> Self {
Self { start, end }
}
@@ -92,6 +97,9 @@ impl<T: Scalar + Copy> LineSegment<T> {
false
}
+ /// Try to find the the point where the two line segments intersect. If they do not intersect,
+ /// `None` is returned. If the lines are parallel and intersect (at least part of a line is on
+ /// a part of the other line), inside that region is returned.
pub fn intersection(line_a: &LineSegment<T>, line_b: &LineSegment<T>) -> Option<Vec2<T>>
where
T: ClosedSub + ClosedMul + ClosedDiv + Zero + RealField,
@@ -104,10 +112,26 @@ impl<T: Scalar + Copy> LineSegment<T> {
let dby = line_b.start.y - line_b.end.y;
// Calculate determinant to see, if the lines are parallel or not
- // TODO: Collinearity check?
let d = (dax * dby) - (day * dbx);
if d == T::zero() {
- None
+ // The two line segments are parallel, check if one may be on the other.
+ if super::triplet_orientation(line_a.start, line_a.end, line_b.start) == TripletOrientation::Collinear
+ && super::triplet_orientation(line_a.start, line_a.end, line_b.end) == TripletOrientation::Collinear
+ {
+ if line_a.contains_collinear(line_b.start) {
+ Some(line_b.start)
+ } else if line_a.contains_collinear(line_b.end) {
+ Some(line_b.end)
+ } else if line_b.contains_collinear(line_a.start) {
+ Some(line_a.start)
+ } else if line_b.contains_collinear(line_a.end) {
+ Some(line_a.end)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
} else {
let ad = (line_a.start.x * line_a.end.y) - (line_a.start.y * line_a.end.x);
let bd = (line_b.start.x * line_b.end.y) - (line_b.start.y * line_b.end.x);
@@ -190,4 +214,27 @@ mod test {
assert!(!segment.contains_collinear(Vec2::new(3., 3.)));
assert!(!segment.contains_collinear(Vec2::new(-1., 0.)));
}
+
+ #[test]
+ fn line_intersection() {
+ let segment_a = LineSegment::new(Vec2::new(-1., -1.), Vec2::new(1., 1.));
+ let segment_b = LineSegment::new(Vec2::new(1., -1.), Vec2::new(-1., 1.));
+ let segment_c = LineSegment::new(Vec2::new(1., -1.), Vec2::new(2., 2.));
+
+ assert!(LineSegment::intersect(&segment_a, &segment_b));
+ assert!(LineSegment::intersect(&segment_b, &segment_c));
+ assert!(!LineSegment::intersect(&segment_a, &segment_c));
+ assert!(LineSegment::intersect(&segment_a, &segment_a));
+
+ assert_eq!(
+ LineSegment::intersection(&segment_a, &segment_b),
+ Some(Vec2::new(0., 0.))
+ );
+ assert_eq!(
+ LineSegment::intersection(&segment_b, &segment_c),
+ Some(Vec2::new(1., -1.))
+ );
+ assert_eq!(LineSegment::intersection(&segment_a, &segment_c), None);
+ assert!(LineSegment::intersection(&segment_a, &segment_a).is_some());
+ }
}
diff --git a/src/math/mod.rs b/src/math/mod.rs
index 829a3c5..c9c1c6e 100644
--- a/src/math/mod.rs
+++ b/src/math/mod.rs
@@ -1,3 +1,5 @@
+//! Useful mathematical operations in graphical contexts.
+
pub mod line_segment;
pub mod polygon;
pub mod rect;
diff --git a/src/math/polygon/mod.rs b/src/math/polygon/mod.rs
index 98b1570..1577f63 100644
--- a/src/math/polygon/mod.rs
+++ b/src/math/polygon/mod.rs
@@ -15,6 +15,7 @@ use std::ops::Neg;
use thiserror::Error;
/// Describes errors that can happen when handling polygons, especially on creation.
+#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum PolygonError<T: Scalar + Copy> {
/// Since the polygon is not allowed to be complex, self intersection is an error.
@@ -28,6 +29,7 @@ pub enum PolygonError<T: Scalar + Copy> {
EdgeNotFound(LineSegment<T>),
}
+/// Describes a non-complex (non-self-intersecting) polygon, currently without holes.
#[derive(Clone, Debug, Deserialize, Serialize)]
// TODO: Support polygons with holes
pub struct Polygon<T: Scalar + Copy> {
diff --git a/src/math/polygon/polygon_graph.rs b/src/math/polygon/polygon_graph.rs
index 5a730b0..fd373dd 100644
--- a/src/math/polygon/polygon_graph.rs
+++ b/src/math/polygon/polygon_graph.rs
@@ -1,3 +1,8 @@
+//! Polygon graphs are used for a more general approach than polygons.
+//!
+//! They are not polygons, but often describe a polygon and make some algorithms on polygons faster
+//! or possible, which may not be practical on the polygon data alone.
+
use super::Polygon;
use crate::math::{self, LineSegment, Vec2};
use nalgebra::{RealField, Scalar};
@@ -16,7 +21,7 @@ struct EdgeIterator<'a, T: Scalar + Copy> {
/// An undirected graph, that is optimised for polygon edge operations. Since edges of a polygon
/// are an undirected graph, this structure also holds both directions. This makes it rather space
-/// inefficient, but makes edge operations rather swift. ß
+/// inefficient, but makes edge operations rather swift.
#[derive(Debug)]
pub struct PolygonGraph<T: Scalar + Copy + PartialOrd> {
/// The nodes of the graph, together with their adjacency list.
diff --git a/src/math/rect.rs b/src/math/rect.rs
index 50c1cb0..6f993d1 100644
--- a/src/math/rect.rs
+++ b/src/math/rect.rs
@@ -1,3 +1,5 @@
+//! Rectangles where the sides are parallel to the x and y-axes.
+
use super::{LineSegment, Polygon, Surface, Vec2};
//use alga::general::{Additive, Identity};
use nalgebra::{ClosedAdd, ClosedSub, RealField, Scalar};
@@ -7,7 +9,7 @@ use serde::{Deserialize, Serialize};
use std::ops::{Add, AddAssign};
/// Represents a Rectangle with the value type T.
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rect<T: Scalar + Copy> {
/// The x coordinate, or leftmost coordinate of the Rect.
pub x: T,
@@ -20,6 +22,8 @@ pub struct Rect<T: Scalar + Copy> {
}
impl<T: Scalar + Copy> Rect<T> {
+ /// Create a new Rectangle from the internal values, where it might be nicer to use a function
+ /// instead of setting the values directly.
pub fn new(x: T, y: T, w: T, h: T) -> Self {
Self { x, y, w, h }
}
diff --git a/src/math/surface.rs b/src/math/surface.rs
index da265d8..ab1c703 100644
--- a/src/math/surface.rs
+++ b/src/math/surface.rs
@@ -1,3 +1,5 @@
+//! Surfaces, which are areas at a certain position in a vector space.
+
use super::{LineSegment, Polygon, Rect, Vec2};
use nalgebra::Scalar;
diff --git a/src/math/triangle.rs b/src/math/triangle.rs
index 35bdcec..b5c1bda 100644
--- a/src/math/triangle.rs
+++ b/src/math/triangle.rs
@@ -1,3 +1,5 @@
+//! Triangles. Nothing more, nothing less.
+
use super::{LineSegment, Vec2};
use alga::general::{ClosedMul, ClosedSub};
use nalgebra::{RealField, Scalar};
diff --git a/src/math/vec2.rs b/src/math/vec2.rs
index d591f1d..b5706a0 100644
--- a/src/math/vec2.rs
+++ b/src/math/vec2.rs
@@ -1,5 +1,8 @@
+//! Two-dimensional vectors and useful operations on them.
+
use crate::math::Rect;
use alga::general::{ClosedAdd, ClosedSub};
+use nalgebra::Point2;
use nalgebra::{RealField, Scalar};
use num_traits::{NumCast, One, ToPrimitive};
use serde::{Deserialize, Serialize};
@@ -8,6 +11,8 @@ use std::convert::{From, Into};
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
use std::{fmt, mem};
+/// Describes a vector, which may be a point or a directed length, depending on the interpretation.
+#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct Vec2<T: Scalar + Copy> {
pub x: T,
@@ -15,10 +20,12 @@ pub struct Vec2<T: Scalar + Copy> {
}
impl<T: Scalar + Copy> Vec2<T> {
+ /// Create a new vector from its internal values.
pub fn new(x: T, y: T) -> Self {
Self { x, y }
}
+ /// Finds the euclidian length and returns it.
pub fn length(&self) -> T
where
T: RealField,
@@ -26,6 +33,9 @@ impl<T: Scalar + Copy> Vec2<T> {
(self.x * self.x + self.y * self.y).sqrt()
}
+ /// Consumes the vector and returns a vector that is rotated by Pi/2 in clockwise direction.
+ /// This is a special case of rotation and the function is faster than using the nonspecific
+ /// rotation function.
pub fn rotated_90_clockwise(mut self) -> Vec2<T>
where
T: One + Neg<Output = T> + MulAssign,
@@ -35,6 +45,9 @@ impl<T: Scalar + Copy> Vec2<T> {
self
}
+ /// Consumes the vector and returns a vector that is rotated by Pi/2 in counterclockwise direction.
+ /// This is a special case of rotation and the function is faster than using the nonspecific
+ /// rotation function.
pub fn rotated_90_counterclockwise(mut self) -> Vec2<T>
where
T: One + Neg<Output = T> + MulAssign,
@@ -45,6 +58,18 @@ impl<T: Scalar + Copy> Vec2<T> {
}
}
+impl<T: Scalar + Copy> Into<Point2<T>> for Vec2<T> {
+ fn into(self) -> Point2<T> {
+ Point2::new(self.x, self.y)
+ }
+}
+
+impl<T: Scalar + Copy> From<Point2<T>> for Vec2<T> {
+ fn from(v: Point2<T>) -> Self {
+ Self::new(v.x, v.y)
+ }
+}
+
// This is sad, but also sadly necessary :/
impl<T> From<raylib::ffi::Vector2> for Vec2<T>
where
diff --git a/src/scaleable.rs b/src/scaleable.rs
deleted file mode 100644
index 450e61e..0000000
--- a/src/scaleable.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-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/svg/mod.rs b/src/svg/mod.rs
index fffc00b..af066f1 100644
--- a/src/svg/mod.rs
+++ b/src/svg/mod.rs
@@ -13,10 +13,10 @@ use style::Style;
use svgtypes::{Path as SVGPath, PathSegment};
use xmltree::{Element, XMLNode};
-// Find the first XML-Node with the given name. With depth, the maximum depth the
-// algorithm will search to can be set. If it is set to `None`, the algorithm will search the
-// entire sub-tree. Returns `None` if no such child can be found. Returns itself, in case the root
-// node is already of the name given.
+/// Find the first XML-Node with the given name. With depth, the maximum depth the
+/// algorithm will search to can be set. If it is set to `None`, the algorithm will search the
+/// entire sub-tree. Returns `None` if no such child can be found. Returns itself, in case the root
+/// node is already of the name given.
pub fn find_first_node(root: Element, name: &str, depth: Option<usize>) -> Option<Element> {
// The abort condition of this recursive function. If the element itself is of the required
// name, return it.
@@ -68,7 +68,10 @@ pub fn read_svg_file<P: AsRef<Path>>(file: P) -> io::Result<Element> {
}
}
+/// Trait that indicates a struct is capable of drawing SVG-elements.
pub trait DrawSVG {
+ /// Draw the elements given by `svg_data` and all its children to the implementor, with the
+ /// specified scale and translated by `position_px`.
fn draw_svg(&mut self, svg_data: &Element, pixels_per_m: f32, position_px: Vec2<f32>);
}
diff --git a/src/svg/style.rs b/src/svg/style.rs
index 78b800d..7a0110e 100644
--- a/src/svg/style.rs
+++ b/src/svg/style.rs
@@ -1,3 +1,8 @@
+//! Style options for SVG components.
+//!
+//! For information on SVG styles, pleas see the SVG documentation.
+// TODO: There should be a lib available that can be integrated into the program
+
use raylib::ffi::Color;
use std::str::FromStr;
@@ -32,6 +37,7 @@ pub fn colour_from_html(html: &str) -> Option<Color> {
/// The style of the end of the stroke.
/// See [stroke-line-cap property](https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty)
/// in the SVG Documentation.
+#[allow(missing_docs)]
pub enum StrokeLineCap {
Butt,
Round,
@@ -41,6 +47,7 @@ pub enum StrokeLineCap {
/// The style of the joining corners of the stroke.
/// See [stroke-line-join property](https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty)
/// in the SVG Documentation
+#[allow(missing_docs)]
pub enum StrokeLineJoin {
Miter,
Round,
@@ -48,6 +55,7 @@ pub enum StrokeLineJoin {
}
/// The style of a path drawing instruction.
+#[allow(missing_docs)]
pub struct Style {
pub fill: Option<Color>,
pub stroke: Color,
diff --git a/src/tool/deletion_tool.rs b/src/tool/deletion_tool.rs
index 5ff3e6a..afd916a 100644
--- a/src/tool/deletion_tool.rs
+++ b/src/tool/deletion_tool.rs
@@ -1,15 +1,26 @@
+//! A meta tool for selecting parts of a map and removing them in a single operation.
+//!
+//! The user can draw a rectangle, which currently must have it's side parallel to the x and y-axes
+//! of the world. With the first node placement, the mode is started, while the second placement would
+//! finish the process and delete all elements that are *completely* contained in the rectangle
+//! (partially contained items are not deleted) or abort it, in which case the selection is removed
+//! and nothing is deleted.
+
use super::Tool;
+use crate::colours::DEFAULT_COLOURS;
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};
+/// The deletion tool itself.
pub struct DeletionTool {
deletion_rect: Option<(Vec2<f64>, Vec2<f64>)>,
}
impl DeletionTool {
+ /// Create a new deletion tool, there should only be one deletion tool and it should be created
+ /// by the editor.
pub fn new() -> Self {
Self {
deletion_rect: None,
@@ -36,15 +47,8 @@ impl Tool for DeletionTool {
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,
- DEFAULT_COLOURS.deletion_rect
- );
- rld.draw_rectangle_lines_ex(
- rect_px,
- 4,
- DEFAULT_COLOURS.deletion_rect_outline
- );
+ rld.draw_rectangle_rec(rect_px, DEFAULT_COLOURS.deletion_rect);
+ rld.draw_rectangle_lines_ex(rect_px, 4, DEFAULT_COLOURS.deletion_rect_outline);
}
}
diff --git a/src/tool/icon_tool.rs b/src/tool/icon_tool.rs
index 09b0ac1..c9e671e 100644
--- a/src/tool/icon_tool.rs
+++ b/src/tool/icon_tool.rs
@@ -1,3 +1,6 @@
+//! Tool for creating icons. For explanation of icons, please see
+//! [the icon module](crate::map::icon).
+
use crate::button::Button;
use crate::config::IconToolKeys;
use crate::map::icon_renderer::IconRenderer;
@@ -8,10 +11,8 @@ use crate::transform::Transform;
use raylib::core::drawing::RaylibDrawHandle;
use std::rc::Rc;
-pub const ICON_DIR: &str = "assets/icons";
-
+/// The icon tool itself.
pub struct IconTool {
- // TODO: support svg
keybindings: IconToolKeys,
/// Saves whether the IconTool is the currently active tool or not.
active: bool,
@@ -22,6 +23,8 @@ pub struct IconTool {
}
impl IconTool {
+ /// Create a new icon tool that renders icons with the provided icon renderer. There should only
+ /// be one instance of the tool for the program, which should be created in the editor.
pub fn new(keybindings: IconToolKeys, renderer: Rc<IconRenderer>) -> Self {
Self {
keybindings,
diff --git a/src/tool/mod.rs b/src/tool/mod.rs
index aeabf19..70534ac 100644
--- a/src/tool/mod.rs
+++ b/src/tool/mod.rs
@@ -1,3 +1,11 @@
+//! Tools, which are user interfaces that must be specifically selected in order to do something.
+//!
+//! As stated, a tool is not simply everything that helps a user do something, think of it more as a
+//! mode which must be elected by the user to perform a task on a specific object type or a class of
+//! objects. If instead the operation is defined by the state of the program, it is not a tool, since
+//! the user didn't explicitly ask for this function to be performed, but it is rather an option
+//! that's inherent to the situation the user finds themselves in.
+
pub mod deletion_tool;
pub mod icon_tool;
pub mod polygon_room_tool;
@@ -20,16 +28,31 @@ use raylib::core::drawing::RaylibDrawHandle;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
#[repr(u8)]
+/// The types of tools available in graf karto. For information about the tool itself, please see the
+/// referenced Tool's documentation.
pub enum ToolType {
+ /// See [super::RectRoomTool] for information on this tool.
RectRoomTool,
+ /// See [super::PolygonRoomTool] for information on this tool.
PolygonRoomTool,
+ /// See [super::WallTool] for information on this tool.
WallTool,
+ /// See [super::IconTool] for information on this tool.
IconTool,
+ /// See [super::DeletionTool] for information on this tool.
DeletionTool,
+ /// See [super::SelectionTool] for information on this tool.
SelectionTool,
+ /// Not a real tool but used to know how many tools are available. New tools must be added
+ /// above this variant.
+ // TODO: Since we now use a hash map in the editor, check if this is still necessary at all.
NumTools,
}
+/// Base trait for tools. A tool is something that performs a specific action on one or more types of
+/// elements. It must be selected in order to be active. For this reason, the selection tool is a
+/// tool (it must be selected from the toolbox), but the dimension indicator for instance is not,
+/// since it is automatically updated when applicable.
pub trait Tool {
/// Code that needs to be called when this Tool is activated or reactivated goes here.
fn activate(&mut self) {}
diff --git a/src/tool/polygon_room_tool.rs b/src/tool/polygon_room_tool.rs
index 1b079d2..33daaf5 100644
--- a/src/tool/polygon_room_tool.rs
+++ b/src/tool/polygon_room_tool.rs
@@ -1,8 +1,10 @@
+//! Tool to create rooms in the shape of generic polygons.
+
use super::Tool;
+use crate::colours::DEFAULT_COLOURS;
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};
struct UnfinishedPolygon {
@@ -10,6 +12,7 @@ struct UnfinishedPolygon {
pub working_corner: Vec2<f64>,
}
+/// The tool itself.
pub struct PolygonRoomTool {
unfinished_polygon: Option<UnfinishedPolygon>,
}
@@ -76,6 +79,8 @@ impl UnfinishedPolygon {
}
impl PolygonRoomTool {
+ /// Create a new polygon room tool. There should be only one instance and it should be created
+ /// in the editor.
pub fn new() -> Self {
Self {
unfinished_polygon: None,
@@ -103,7 +108,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,
- DEFAULT_COLOURS.room_selected
+ DEFAULT_COLOURS.room_selected,
);
} else if polygon.corners.len() == 2 {
// We have three valid corners, so we can draw a triangle.
@@ -111,7 +116,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),
- DEFAULT_COLOURS.room_selected
+ DEFAULT_COLOURS.room_selected,
)
} else {
// A proper polygon can be drawn.
@@ -126,7 +131,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]),
- DEFAULT_COLOURS.room_selected
+ DEFAULT_COLOURS.room_selected,
)
}
} else if polygon.check_validity_completed().is_ok() {
@@ -138,7 +143,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]),
- DEFAULT_COLOURS.room_selected
+ DEFAULT_COLOURS.room_selected,
)
}
}
diff --git a/src/tool/rect_room_tool.rs b/src/tool/rect_room_tool.rs
index dfda495..7cb85bb 100644
--- a/src/tool/rect_room_tool.rs
+++ b/src/tool/rect_room_tool.rs
@@ -1,10 +1,15 @@
+//! The rectangle room tool is a specialised tool to create rooms of rectangular shape and with the
+//! sides of the room parallel to the x and y-axes. This is often useful, when a quick room creation
+//! is necessary and the shape of the room does not have to be very special.
+
use super::Tool;
+use crate::colours::DEFAULT_COLOURS;
use crate::map::Map;
use crate::math::{Rect, Vec2};
use crate::transform::Transform;
-use crate::colours::DEFAULT_COLOURS;
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
+/// The tool to create simple, rectangular rooms.
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.
@@ -12,7 +17,8 @@ pub struct RectRoomTool {
}
impl RectRoomTool {
- /// Create a new room tool where no rooms have been drawn yet.
+ /// Create a new room tool where no rooms have been drawn yet. Should be created only once per
+ /// program instance and by the editor.
pub fn new() -> Self {
Self {
unfinished_rect: None,
@@ -35,7 +41,7 @@ impl Tool for RectRoomTool {
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
+ DEFAULT_COLOURS.room_selected,
);
}
}
diff --git a/src/tool/selection_tool.rs b/src/tool/selection_tool.rs
index 49efba9..c790734 100644
--- a/src/tool/selection_tool.rs
+++ b/src/tool/selection_tool.rs
@@ -1,15 +1,26 @@
+//! Selection of items on the map.
+//!
+//! When selecting items on the map, the editor goes into a different mode than when editing a
+//! specific kind of item. Actions that are available for specific types of items become
+//! unavailable, while other actions that make use of the properties to a wide range of items
+//! become available instead.
+//! For this reason, the selection tool can be thought of as a kind of meta tool over tools.
+
use super::Tool;
+use crate::colours::DEFAULT_COLOURS;
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;
+/// The selection tool makes it possible to select any item on the map when activated.
pub struct SelectionTool {
selection_rect: Option<(Vec2<f64>, Vec2<f64>)>,
}
impl SelectionTool {
+ /// Create a new selection tool. There should be only one such tool per program instance and it
+ /// should be created in the editor.
pub fn new() -> Self {
Self {
selection_rect: None,
@@ -31,15 +42,8 @@ impl Tool for SelectionTool {
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
- );
+ rld.draw_rectangle_rec(rect_px, DEFAULT_COLOURS.selection_rect);
+ rld.draw_rectangle_lines_ex(rect_px, 4, DEFAULT_COLOURS.selection_rect_outline);
}
}
diff --git a/src/tool/wall_tool.rs b/src/tool/wall_tool.rs
index b958799..123171c 100644
--- a/src/tool/wall_tool.rs
+++ b/src/tool/wall_tool.rs
@@ -1,3 +1,6 @@
+//! Tool to create walls. For information about walls, see also
+//! [the wall module](crate::map::wall).
+
use super::Tool;
use crate::map::Map;
use crate::math::{LineSegment, Vec2};
@@ -5,11 +8,14 @@ use crate::transform::Transform;
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
use raylib::ffi::{Color, Vector2};
+/// The wall tool to create solid barriers a player usually cannot cross.
pub struct WallTool {
unfinished_wall: Option<LineSegment<f64>>,
}
impl WallTool {
+ /// Create a new wall tool. There should only be one wall tool per program instance, which should
+ /// be created inside of the editor.
pub fn new() -> Self {
Self {
unfinished_wall: None,
diff --git a/src/transform.rs b/src/transform.rs
index 7c1adbf..147956c 100644
--- a/src/transform.rs
+++ b/src/transform.rs
@@ -9,6 +9,8 @@ const STANDARD_PIXELS_PER_M: f64 = 64.;
const MIN_PIXELS_PER_M: f64 = 5.;
const MAX_PIXELS_PER_M: f64 = 10_000.;
+/// A rigid 2D transformation. Since the translation must often be accessed directly and so far there
+/// was no huge need for fancy transformation, this currently does not use any matrix transformations.
pub struct Transform {
/// The (not necessarily natural) number of pixels per m, i.e. the current scale of the map
pixels_per_m: f64,
@@ -119,9 +121,15 @@ impl Transform {
self.translation_px += *by;
}
+ /// Get the current scale with the number of pixels (as a real number) that makes up a meter of
+ /// actual map space.
pub fn pixels_per_m(&self) -> f64 {
self.pixels_per_m
}
+ /// Get the current translation of the map on the screen. This is purely in pixels, rather than
+ /// meters, since a translation in meters does not make sense, because all map items have an
+ /// absolute position and the translation is merely used to see where on the screen anything
+ /// should be drawn.
pub fn translation_px(&self) -> &Vec2<f64> {
&self.translation_px
}
diff --git a/src/transformable.rs b/src/transformable.rs
new file mode 100644
index 0000000..b8212f9
--- /dev/null
+++ b/src/transformable.rs
@@ -0,0 +1,17 @@
+//! Traits for items that can be modified using a variety of matrix transformations.
+//!
+//! Items in the world may be resizable or rotateable etc. in a variety of different ways. This
+//! module contains the traits that are used to interact with such objects in a way, that ensures the
+//! item is not transformed illegaly. For example, an item may be scaleable by a factor, but that does
+//! not necessarily mean that it can be scaled by different factors in x and y direction (stretched).
+//! If need be, this module contains the different types of transformations and transformation
+//! restrictions to make these features available and restricting them reliably.
+
+use nalgebra::Matrix3;
+
+/// Trait for things that can be stretched and rotated etc. as one pleases without
+/// becoming invalid. A room for instance would fall into this category, while an icon might not.
+pub trait NonRigidTransformable {
+ /// Applies the provided matrix to all vertices of this transformable element.
+ fn apply_matrix(&mut self, matrix: &Matrix3<f64>);
+}