use crate::{ensure_element, ensure_tag_name, get_attribute};
use anyhow::anyhow;
use rgb::RGB8;
use super::property::Properties;
pub enum ObjectAlignment {
Unspecified,
TopLeft,
Top,
TopRight,
Left,
Center,
Right,
BottomLeft,
Bottom,
BottomRight,
}
impl Default for ObjectAlignment {
fn default() -> Self {
ObjectAlignment::Unspecified
}
}
pub struct Frame {
/// The local ID of a tile within the parent <tileset>.
tileid: u32,
// How long (in milliseconds) this frame should be displayed before advancing to the next frame.
duration: u32,
}
/// Define properties for a specific tile
pub struct Tile {
/// The local tile ID within its tileset.
pub id: u32,
/// The type of the tile.
/// Refers to an object type and is used by tile objects. (optional) (since 1.0)
// TODO pub tile_type: Option<>,
/// Defines the terrain type of each corner of the tile,
/// given as comma-separated indexes in the terrain types array
/// in the order top-left, top-right, bottom-left, bottom-right.
/// Leaving out a value means that corner has no terrain. (optional)
// TODO pub terrain: Option<>,
/// A percentage indicating the probability that this tile is chosen
/// when it competes with others while editing with the terrain tool.
/// (defaults to 0)
pub probability: f64,
pub properties: super::property::Properties,
//TODO Can contain at most one: <image> (since 0.9), <objectgroup>, <animation>
pub image: Option<super::image::ImageSource>,
/// Each tile can have exactly one animation associated with it.
/// In the future, there could be support for multiple named animations on a tile.
pub animation: Option<Vec<Frame>>,
}
/// This element is used to specify an offset in pixels,
/// to be applied when drawing a tile from the related tileset.
/// When not present, no offset is applied.
#[derive(Eq, PartialEq, Debug)]
pub struct TileOffset {
/// Horizontal offset in pixels. (defaults to 0)
x: isize,
/// Vertical offset in pixels (positive is down, defaults to 0)
y: isize,
}
impl TileOffset {
fn from_xml(node: roxmltree::Node) -> anyhow::Result<TileOffset> {
ensure_element!(node);
ensure_tag_name!(node, "tileoffset");
Ok(TileOffset {
x: get_attribute!(node, "x")?.parse()?,
y: get_attribute!(node, "y")?.parse()?,
})
}
}
impl Default for TileOffset {
fn default() -> Self {
TileOffset { x: 0, y: 0 }
}
}
#[cfg(test)]
mod test_tile_offset {
use crate::lowlevel::tileset::TileOffset;
#[test]
fn default_value() {
let default: TileOffset = Default::default();
assert_eq!(default.x, 0);
assert_eq!(default.y, 0);
}
#[test]
fn from_xml() {
crate::parse_property_test!(
TileOffset,
r##"<tileoffset x="32" y="16" />"##,
TileOffset { x: 32, y: 16 }
);
}
}
/// Orientation of the grid for the tiles in this tileset (orthogonal or isometric, defaults to orthogonal)
#[derive(Eq, PartialEq, Debug)]
pub enum GridOrientation {
Orthogonal,
Isometric,
}
impl Default for GridOrientation {
fn default() -> Self {
GridOrientation::Orthogonal
}
}
/// This element is only used in case of isometric orientation,
/// and determines how tile overlays for terrain and collision information are rendered.
#[derive(Eq, PartialEq, Debug)]
pub struct Grid {
orientation: GridOrientation,
/// Width of a grid cell
width: u32,
/// Height of a grid cell
height: u32,
}
impl Grid {
fn from_xml(node: roxmltree::Node) -> anyhow::Result<Grid> {
ensure_element!(node);
ensure_tag_name!(node, "grid");
let orientation: GridOrientation = match node.attribute("orientation") {
Some("orthogonal") => GridOrientation::Orthogonal,
Some("isometric") => GridOrientation::Isometric,
_ => GridOrientation::default(),
};
Ok(Grid {
orientation,
width: get_attribute!(node, "width")?.parse()?,
height: get_attribute!(node, "height")?.parse()?,
})
}
}
#[cfg(test)]
mod test_grid {
use crate::lowlevel::tileset::{Grid, GridOrientation};
#[test]
fn grid_orientation_default_value() {
let default: GridOrientation = Default::default();
assert_eq!(default, GridOrientation::Orthogonal);
}
#[test]
fn from_xml() {
crate::parse_property_test!(
Grid,
r##"<grid orientation="isometric" height="32" width="32" />"##,
Grid {
orientation: GridOrientation::Isometric,
height: 32,
width: 32
}
);
crate::parse_property_test!(
Grid,
r##"<grid orientation="orthogonal" height="32" width="32" />"##,
Grid {
orientation: GridOrientation::Orthogonal,
height: 32,
width: 32
}
);
}
#[test]
fn from_xml_without_explicit_orientation() {
crate::parse_property_test!(
Grid,
r##"<grid height="32" width="32" />"##,
Grid {
orientation: GridOrientation::default(),
height: 32,
width: 32
}
);
}
}
#[derive(PartialEq, Debug)]
pub struct Terrain {
/// The name of the terrain type.
pub name: String,
/// The local tile-id of the tile that represents the terrain visually.
pub tile: u32,
pub properties: Properties,
}
impl Terrain {
fn from_xml(node: roxmltree::Node) -> anyhow::Result<Terrain> {
ensure_element!(node);
ensure_tag_name!(node, "terrain");
let name = get_attribute!(node, "name")?.to_string();
let tile = get_attribute!(node, "tile")?.parse()?;
let properties: Properties = match node.children().find(|&c| c.has_tag_name("properties")) {
Some(node) => Properties::from_xml(node)?,
None => Properties::default(),
};
Ok(Terrain {
name,
tile,
properties,
})
}
}
/// A color that can be used to define the corner and/or edge of a Wang tile.
#[derive(PartialEq, Debug)]
pub struct WangColor {
/// The name of this color.
pub name: String,
/// The color in #RRGGBB format (example: #c17d11).
pub color: RGB8,
/// The local tile ID of the tile representing this color.
pub tile: u32,
/// The relative probability that this color is chosen over others in case of multiple options.
/// (defaults to 0)
pub probability: f64,
// Can contain at most one: <properties>
pub properties: Properties,
}
/// “The Wang ID, given by a comma-separated list of indexes
/// (starting from 1, because 0 means _unset_)
/// referring to the Wang colors in the Wang set in the following order:
/// top, top right, right, bottom right, bottom, bottom left, left, top left (since Tiled 1.5).
/// Before Tiled 1.5, the Wang ID was saved as a 32-bit unsigned integer
/// stored in the format 0xCECECECE (where each C is a corner color and each E is an edge color,
/// in reverse order).”
#[derive(PartialEq, Debug)]
pub struct WangId {}
///Defines a Wang tile, by referring to a tile in the tileset and associating it with a certain Wang ID.
#[derive(PartialEq, Debug)]
pub struct WangTile {
/// The tile ID.
pub tileid: u32,
/// The Wang ID
pub wangid: WangId,
/// Whether the tile is flipped horizontally (removed in Tiled 1.5).
pub hflip: bool,
/// Whether the tile is flipped vertically (removed in Tiled 1.5).
pub vflip: bool,
/// Whether the tile is flipped on its diagonal (removed in Tiled 1.5).
pub dflip: bool,
}
/// Defines a list of corner colors and a list of edge colors,
/// and any number of Wang tiles using these colors.
#[derive(PartialEq, Debug)]
pub struct WangSet {
///The name of the Wang set.
pub name: String,
/// The tile ID of the tile representing this Wang set.
pub tile: u32,
// Can contain at most one: <properties>
pub properties: Properties,
/// Can contain up to 255: <wangcolor> (since Tiled 1.5)
pub wangcolor: Vec<WangColor>,
/// Can contain any number: <wangtile>
pub wangtiles: Vec<WangTile>,
}
/// This element is used to describe which transformations can be applied to the tiles
/// (e.g. to extend a Wang set by transforming existing tiles).
#[derive(PartialEq, Debug)]
pub struct TileSetTransformations {
/// Whether the tiles in this set can be flipped horizontally (default 0)
pub hflip: bool,
/// Whether the tiles in this set can be flipped vertically (default 0)
pub vflip: bool,
/// Whether the tiles in this set can be rotated in 90 degree increments (default 0)
pub rotate: bool,
/// Whether untransformed tiles remain preferred, otherwise transformed tiles are used to produce more variations (default 0)
pub prefer_untransformed: bool,
}
impl Default for TileSetTransformations {
fn default() -> Self {
TileSetTransformations {
hflip: false,
vflip: false,
rotate: false,
prefer_untransformed: false,
}
}
}
impl TileSetTransformations {
pub fn from_xml(node: roxmltree::Node) -> anyhow::Result<TileSetTransformations> {
ensure_element!(node);
ensure_tag_name!(node, "transformations");
macro_rules! num_string_to_bool {
($attribute_name:expr) => {
match node.attribute($attribute_name) {
Some("1") => Ok(true),
Some("0") => Ok(false),
Some(val) => Err(anyhow!(
"Unexpected value \"{}\" for {}. Only 0->false and 1->true are allowed",
val,
$attribute_name
)),
None => Ok(false),
}
};
}
Ok(TileSetTransformations {
hflip: num_string_to_bool!("hflip")?,
vflip: num_string_to_bool!("vflip")?,
rotate: num_string_to_bool!("rotate")?,
prefer_untransformed: num_string_to_bool!("preferuntransformed")?,
})
}
}
//TODO TileSetTransformations tests (parse test and test for default)
#[cfg(test)]
mod test_transformations {
use crate::lowlevel::tileset::TileSetTransformations;
#[test]
fn default_value() {
let default: TileSetTransformations = Default::default();
assert_eq!(
default,
TileSetTransformations {
hflip: false,
vflip: false,
rotate: false,
prefer_untransformed: false,
}
);
}
#[test]
fn from_xml() {
crate::parse_property_test!(
TileSetTransformations,
r##"<transformations hflip="1" vflip="1" rotate="1" preferuntransformed="1" />"##,
TileSetTransformations {
hflip: true,
vflip: true,
rotate: true,
prefer_untransformed: true,
}
);
crate::parse_property_test!(
TileSetTransformations,
r##"<transformations hflip="1" vflip="0" rotate="1" preferuntransformed="0" />"##,
TileSetTransformations {
hflip: true,
vflip: false,
rotate: true,
prefer_untransformed: false,
}
);
}
}
pub struct TileSet {
/// The name of this tileset.
pub name: String,
/// The (maximum) width of the tiles in this tileset.
pub tile_width: usize,
/// The (maximum) height of the tiles in this tileset.
pub tile_height: usize,
/// The spacing in pixels between the tiles in this tileset (applies to the tileset image, defaults to 0)
pub spacing: usize,
/// The margin around the tiles in this tileset (applies to the tileset image, defaults to 0)
pub margin: usize,
/// The number of tiles in this tileset (since 0.13)
pub tile_count: usize,
/// The number of tile columns in the tileset.
/// For image collection tilesets it is editable and is used when displaying the tileset. (since 0.15)
pub columns: usize,
/// Controls the alignment for tile objects.
/// Valid values are unspecified, topleft, top, topright, left, center, right, bottomleft, bottom and bottomright.
/// The default value is unspecified, for compatibility reasons.
/// When unspecified, tile objects use bottomleft in orthogonal mode and bottom in isometric mode. (since 1.4)
pub object_alignment: ObjectAlignment,
/// Define properties for specific tiles.
/// This doesn't necessarily contain all tiles (only tiles that have properties, like animations or image tiles)
/// In fact most of the time it doesn't contain tiles.
pub tile: Vec<Tile>,
pub image: Option<super::image::Image>,
/// this field is optional, if not set it will get {x:0, y:0} (no offset)
pub tile_offset: TileOffset,
/// (since 1.0)
pub grid: Option<Grid>,
pub properties: super::property::Properties,
pub terrain_types: Option<Vec<Terrain>>,
/// (since 1.1)
pub wangsets: Option<Vec<WangSet>>,
/// (since 1.5)
pub transformations: TileSetTransformations,
}
pub enum TileSetElement {
Embedded { first_gid: usize, tileset: TileSet },
External { first_gid: usize, source: String },
}