summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLawnCable <git@lawncable.net>2022-04-15 08:36:36 +0200
committerLawnCable <git@lawncable.net>2022-04-15 08:36:36 +0200
commitec09c0ae43799846a68a4e100a68353730edfed1 (patch)
treeaddf32caaad9b2968abb633476b02ed31e85f50f /src
downloadfast-tiled.rs-ec09c0ae43799846a68a4e100a68353730edfed1.tar.gz
fast-tiled.rs-ec09c0ae43799846a68a4e100a68353730edfed1.tar.bz2
fast-tiled.rs-ec09c0ae43799846a68a4e100a68353730edfed1.tar.xz
fast-tiled.rs-ec09c0ae43799846a68a4e100a68353730edfed1.zip
init
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs3
-rw-r--r--src/lowlevel/map.rs176
-rw-r--r--src/lowlevel/mod.rs3
-rw-r--r--src/lowlevel/property.rs201
4 files changed, 383 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..3c9002e
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,3 @@
+
+
+pub mod lowlevel; \ No newline at end of file
diff --git a/src/lowlevel/map.rs b/src/lowlevel/map.rs
new file mode 100644
index 0000000..c2a33cf
--- /dev/null
+++ b/src/lowlevel/map.rs
@@ -0,0 +1,176 @@
+use anyhow::anyhow;
+use rgb::RGBA;
+
+// https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map
+
+/// Map orientation
+/// Tiled supports “orthogonal”, “isometric”, “staggered” and “hexagonal” (since 0.11).
+pub enum MapOrientation {
+ Orthogonal,
+ Isometric,
+ Staggered,
+ Hexagonal,
+}
+
+impl MapOrientation {
+ pub fn from_string(string: &str) -> anyhow::Result<MapOrientation> {
+ match string {
+ "orthogonal" => Ok(MapOrientation::Orthogonal),
+ "isometric" => Ok(MapOrientation::Isometric),
+ "staggered" => Ok(MapOrientation::Staggered),
+ "hexagonal" => Ok(MapOrientation::Hexagonal),
+ _ => Err(anyhow!("Unknown MapOrientation: {}", string)),
+ }
+ }
+
+ pub fn to_string(self) -> String {
+ match self {
+ MapOrientation::Orthogonal => "orthogonal".to_owned(),
+ MapOrientation::Isometric => "isometric".to_owned(),
+ MapOrientation::Staggered => "staggered".to_owned(),
+ MapOrientation::Hexagonal => "hexagonal".to_owned(),
+ }
+ }
+}
+
+/// The order in which tiles on tile layers are rendered.
+/// In all cases, the map is drawn row-by-row. (only supported for orthogonal maps at the moment)
+pub enum MapRenderOrder {
+ /// RightDown - The default
+ RightDown,
+ RightUp,
+ LeftDown,
+ LeftUp,
+}
+
+impl Default for MapRenderOrder {
+ fn default() -> Self {
+ MapRenderOrder::RightDown
+ }
+}
+
+impl MapRenderOrder {
+ pub fn from_string(string: &str) -> anyhow::Result<MapRenderOrder> {
+ match string {
+ "right-down" => Ok(MapRenderOrder::RightDown),
+ "right-up" => Ok(MapRenderOrder::RightUp),
+ "left-down" => Ok(MapRenderOrder::LeftDown),
+ "left-up" => Ok(MapRenderOrder::LeftUp),
+ _ => Err(anyhow!("Unknown MapRenderOrder: {}", string)),
+ }
+ }
+
+ pub fn to_string(self) -> String {
+ match self {
+ MapRenderOrder::RightDown => "right-down".to_owned(),
+ MapRenderOrder::RightUp => "right-up".to_owned(),
+ MapRenderOrder::LeftDown => "left-down".to_owned(),
+ MapRenderOrder::LeftUp => "left-up".to_owned(),
+ }
+ }
+}
+
+/// For staggered and hexagonal maps, determines which axis (“x” or “y”) is staggered. (since 0.11)
+pub enum StaggerAxis {
+ X,
+ Y,
+}
+
+impl StaggerAxis {
+ pub fn from_string(string: &str) -> anyhow::Result<StaggerAxis> {
+ match string {
+ "x" => Ok(StaggerAxis::X),
+ "y" => Ok(StaggerAxis::Y),
+ _ => Err(anyhow!("invalid StaggerAxis: {}", string)),
+ }
+ }
+
+ pub fn to_string(self) -> String {
+ match self {
+ StaggerAxis::X => "x".to_owned(),
+ StaggerAxis::Y => "y".to_owned(),
+ }
+ }
+}
+
+/// For staggered and hexagonal maps, determines whether the “even” or “odd” indexes along the staggered axis are shifted. (since 0.11)
+pub enum StaggerIndex {
+ Even,
+ Odd,
+}
+
+impl StaggerIndex {
+ pub fn from_string(string: &str) -> anyhow::Result<StaggerIndex> {
+ match string {
+ "even" => Ok(StaggerIndex::Even),
+ "odd" => Ok(StaggerIndex::Odd),
+ _ => Err(anyhow!("invalid StaggerIndex: {}", string)),
+ }
+ }
+
+ pub fn to_string(self) -> String {
+ match self {
+ StaggerIndex::Even => "even".to_owned(),
+ StaggerIndex::Odd => "odd".to_owned(),
+ }
+ }
+}
+
+pub struct Map {
+ /// The TMX format version. Was “1.0” so far, and will be incremented to match minor Tiled releases.
+ pub version: String,
+
+ /// The Tiled version used to save the file (since Tiled 1.0.1). May be a date (for snapshot builds). (optional)
+ pub tiledversion: Option<String>,
+
+ /// Map orientation. Tiled supports “orthogonal”, “isometric”, “staggered” and “hexagonal” (since 0.11).
+ pub orientation: MapOrientation,
+
+ /// The order in which tiles on tile layers are rendered.
+ pub renderorder: MapRenderOrder,
+
+ /// The compression level to use for tile layer data (defaults to -1, which means to use the algorithm default).
+ pub compressionlevel: Option<isize>, // TODO seems optional, please validate
+
+ // The map width in tiles.
+ pub width: usize,
+ /// The map height in tiles.
+ pub height: usize,
+
+ /// The width of a tile.
+ pub tilewidth: usize,
+ /// The height of a tile.
+ pub tileheight: usize,
+ /// Only for hexagonal maps. Determines the width or height (depending on the staggered axis) of the tile’s edge, in pixels.
+ pub hexsidelength: Option<usize>,
+
+ /// For staggered and hexagonal maps, determines which axis (“x” or “y”) is staggered. (since 0.11)
+ pub staggeraxis: Option<StaggerAxis>,
+ /// For staggered and hexagonal maps, determines whether the “even” or “odd” indexes along the staggered axis are shifted. (since 0.11)
+ pub staggerindex: Option<StaggerIndex>,
+
+ /// The background color of the map. (optional, may include alpha value since 0.15 in the form #AARRGGBB. Defaults to fully transparent.)
+ pub backgroundcolor: Option<RGBA<u8>>,
+
+ /// Stores the next available ID for new layers. This number is stored to prevent reuse of the same ID after layers have been removed. (since 1.2) (defaults to the highest layer id in the file + 1)
+ pub nextlayerid: usize, // TODO set default in funtions
+ /// Stores the next available ID for new objects. This number is stored to prevent reuse of the same ID after objects have been removed. (since 0.11) (defaults to the highest object id in the file + 1)
+ pub nextobjectid: usize,
+
+ /// Whether this map is infinite. An infinite map has no fixed size and can grow in all directions. Its layer data is stored in chunks. (0 for false, 1 for true, defaults to 0)
+ pub infinite: bool,
+
+ // xml child elements
+ pub properties: Option<super::property::Properties>,
+ // TODO Can contain any number: <tileset>, <layer>, <objectgroup>, <imagelayer>, <group> (since 1.0), <editorsettings> (since 1.3)
+}
+
+impl Map {
+ pub fn from_xml(doc: roxmltree::Document) -> anyhow::Result<Map> {
+ doc.root_element();
+
+ assert!(doc.root_element().has_tag_name("map"));
+
+ Err(anyhow!("not implemented"))
+ }
+}
diff --git a/src/lowlevel/mod.rs b/src/lowlevel/mod.rs
new file mode 100644
index 0000000..1a10f8d
--- /dev/null
+++ b/src/lowlevel/mod.rs
@@ -0,0 +1,3 @@
+pub mod map;
+
+pub mod property;
diff --git a/src/lowlevel/property.rs b/src/lowlevel/property.rs
new file mode 100644
index 0000000..26dcedd
--- /dev/null
+++ b/src/lowlevel/property.rs
@@ -0,0 +1,201 @@
+use anyhow::anyhow;
+use rgb::RGBA8;
+
+// https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#property
+
+// name: The name of the property.
+// type: The type of the property. Can be string (default), int, float, bool, color, file or object (since 0.16, with color and file added in 0.17, and object added in 1.4).
+// value: The value of the property. (default string is “”, default number is 0, default boolean is “false”, default color is #00000000, default file is “.” (the current file’s parent directory))
+
+#[derive(Debug, PartialEq)]
+pub enum Property {
+ String { name: String, value: String },
+ Int { name: String, value: isize },
+ Float { name: String, value: f64 },
+ Bool { name: String, value: bool },
+ Color { name: String, value: RGBA8 },
+ File { name: String, value: String },
+ Object { name: String, value: usize },
+}
+
+impl Property {
+ pub fn from_xml(node: roxmltree::Node) -> anyhow::Result<Property> {
+ if node.node_type() != roxmltree::NodeType::Element {
+ return Err(anyhow!("xml -> node is not an element"));
+ }
+
+ let name = node
+ .attribute("name")
+ .ok_or(anyhow!("property name missing"))?
+ .to_owned();
+ let property_type = node
+ .attribute("type")
+ .ok_or(anyhow!("property type missing"))?;
+
+ // handle the case that 'string' value is stored in element content instead of value atribute
+ if property_type == "string" {
+ if let Some(child) = node.first_child() {
+ if let Some(text) = child.text() {
+ return Ok(Property::String {
+ name,
+ value: text.to_owned(),
+ });
+ } else {
+ return Err(anyhow!("property element content is not of NodeType::Text"));
+ }
+ }
+ }
+
+ let raw_value = node
+ .attribute("value")
+ .ok_or(anyhow!("property value missing"))?;
+
+ match property_type {
+ "string" => Ok(Property::String {
+ name,
+ value: raw_value.to_owned(),
+ }),
+ "int" => Ok(Property::Int {
+ name,
+ value: raw_value.parse()?,
+ }),
+ "float" => Ok(Property::Float {
+ name,
+ value: raw_value.parse()?,
+ }),
+ "bool" => {
+ let value = match raw_value {
+ "true" => Ok(true),
+ "false" => Ok(false),
+ _ => Err(anyhow!(
+ "boolean property has unexpected value: {}",
+ raw_value
+ )),
+ };
+ Ok(Property::Bool {
+ name,
+ value: value?,
+ })
+ }
+ "color" => {
+ let mut chars = raw_value.chars();
+ chars.next(); // remove the `#`
+ if let Some(color) = read_color::rgba(&mut chars) {
+ Ok(Property::Color {
+ name,
+ value: RGBA8 {
+ r: color[0],
+ g: color[1],
+ b: color[2],
+ a: color[3],
+ },
+ })
+ } else {
+ Err(anyhow!("could not parse color"))
+ }
+ }
+ "file" => Ok(Property::File {
+ name,
+ value: raw_value.to_owned(),
+ }),
+ "object" => Ok(Property::Object {
+ name,
+ value: raw_value.parse()?,
+ }),
+ _ => Err(anyhow!("unknown type {}", property_type)),
+ }
+ }
+
+ pub fn value_to_string(self) -> String {
+ match self {
+ Property::String { value, .. } => value,
+ Property::Int { value, .. } => format!("{}", value),
+ Property::Float { value, .. } => format!("{}", value),
+ Property::Bool { value, .. } => match value {
+ true => "true",
+ false => "false",
+ }
+ .to_owned(),
+ Property::Color { value, .. } => {
+ format!("#{:x}{:x}{:x}{:x}", value.r, value.g, value.b, value.a)
+ }
+ Property::File { value, .. } => value,
+ Property::Object { value, .. } => format!("{}", value),
+ }
+ }
+}
+
+// Wraps any number of custom properties
+#[derive(Debug)]
+pub struct Properties {
+ pub properties: Vec<Property>,
+}
+
+impl Properties {
+ fn from_xml(node: roxmltree::Node) -> anyhow::Result<Properties> {
+ Err(anyhow!("not implemented"))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::lowlevel::property::Property;
+ use rgb::RGBA8;
+
+ #[test]
+ fn parse_color_property() {
+ // Find element by id
+ let doc = roxmltree::Document::parse(
+ r##"<property name="enemyTint" type="color" value="#ffa33636"/>"##,
+ )
+ .unwrap();
+ let elem = doc.root_element();
+ assert_eq!(
+ Property::from_xml(elem).unwrap(),
+ Property::Color {
+ name: "enemyTint".to_owned(),
+ value: RGBA8 {
+ r: 255,
+ g: 163,
+ b: 54,
+ a: 54
+ }
+ }
+ );
+ }
+
+ #[test]
+ fn parse_string_property_inside_attribute() {
+ // Find element by id
+ let doc = roxmltree::Document::parse(
+ r##"<property name="enemyText" type="string" value="hello world"/>"##,
+ )
+ .unwrap();
+ let elem = doc.root_element();
+ assert_eq!(
+ Property::from_xml(elem).unwrap(),
+ Property::String {
+ name: "enemyText".to_owned(),
+ value: "hello world".to_owned()
+ }
+ );
+ }
+
+ #[test]
+ fn parse_string_property_inside_element() {
+ // Find element by id
+ let doc = roxmltree::Document::parse(
+ r##"<property name="enemyText" type="string">hello world</property>"##,
+ )
+ .unwrap();
+ let elem = doc.root_element();
+ println!("{:?}", elem.first_child());
+ assert_eq!(
+ Property::from_xml(elem).unwrap(),
+ Property::String {
+ name: "enemyText".to_owned(),
+ value: "hello world".to_owned()
+ }
+ );
+ }
+}