use crate::{
Result, load_file,
error::QuicksilverError,
geom::{Rectangle, Shape, Vector},
graphics::{Image, ImageError}
};
use futures::{Future, future};
use std::{
cmp::Ordering,
collections::HashMap,
error::Error,
fmt::{Display, Formatter, Result as FmtResult},
io::Error as IOError,
num::ParseIntError,
path::{Path, PathBuf},
str::{FromStr, ParseBoolError}
};
#[derive(Clone)]
struct Region {
image: usize,
name: String,
rotate: bool,
region: Rectangle,
center: Vector,
index: i32
}
#[derive(Clone, Debug)]
pub struct Atlas {
data: HashMap<String, AtlasItem>
}
impl Atlas {
pub fn load<'a, P: 'static + AsRef<Path>>(path: P) -> impl Future<Item = Atlas, Error = QuicksilverError> {
load_file(PathBuf::from(path.as_ref()))
.map(move |data| {
let path = path.as_ref();
let data = match String::from_utf8(data) {
Ok(string) => string,
Err(_) => return Err(AtlasError::ParseError("Failed to parse provided file as UTF8 text").into())
};
let mut lines = data.lines();
let mut images = Vec::new();
let mut regions = Vec::new();
let directory: &Path = if let Some(parent) = path.parent() { parent } else { path.as_ref() };
while let Some(line) = lines.next() {
use std::path::PathBuf;
let path: PathBuf = [directory, &Path::new(line)].iter().collect();
images.push(Image::load(path));
for _ in 0..4 {
getval(&mut lines)?;
}
while let Some(line) = lines.next() {
if line.len() == 0 { break; }
let name = line.to_owned();
let mut rotate = get_values_from_line(getval(&mut lines)?)?;
let mut xy = get_values_from_line::<i32>(getval(&mut lines)?)?;
let mut size = get_values_from_line::<i32>(getval(&mut lines)?)?;
let mut line = getval(&mut lines)?;
while !line.contains("orig") {
line = getval(&mut lines)?;
}
let mut orig = get_values_from_line::<i32>(line)?;
let mut offset = get_values_from_line::<i32>(getval(&mut lines)?)?;
let index = getval(&mut get_values_from_line(getval(&mut lines)?)?)??;
let rotate = getval(&mut rotate)??;
let region = Rectangle::new((getval(&mut xy)??, getval(&mut xy)??), (getval(&mut size)??, getval(&mut size)??));
let original_size = Vector::new(getval(&mut orig)??, getval(&mut orig)??);
let offset = Vector::new(getval(&mut offset)??, getval(&mut offset)??);
let center = region.center() + (original_size - region.size() - offset.x_comp() + offset.y_comp());
let image = images.len() - 1;
regions.push(Region { image, name, region, rotate, center, index });
}
}
Ok((future::join_all(images), regions))
})
.and_then(future::result)
.and_then(|(image_loader, regions)| image_loader.map(|images| (images, regions)))
.map(create)
}
pub fn get(&self, name: &str) -> Option<AtlasItem> {
Some(self.data.get(name)?.clone())
}
}
fn get_values_from_line<'a, T: FromStr>(line: &'a str) -> Result<impl 'a + Iterator<Item = Result<T>>> {
let mut split = line.split(": ");
getval(&mut split)?;
Ok(getval(&mut split)?.split(", ").map(|item| match item.parse() {
Ok(item) => Ok(item),
Err(_) => Err(AtlasError::ParseError("Failed to parse item in manifest").into())
}))
}
fn getval<T>(lines: &mut impl Iterator<Item = T>) -> Result<T> {
match lines.next() {
Some(val) => Ok(val),
None => Err(AtlasError::ParseError("Unexpected end of data").into())
}
}
fn create(data: (Vec<Image>, Vec<Region>)) -> Atlas {
let (images, regions) = data;
let mut images = regions.into_iter()
.map(|region|
(region.name, region.index, images[region.image].subimage(region.region)))
.collect::<Vec<(String, i32, Image)>>();
images.sort_by(|a: &(String, i32, Image), b: &(String, i32, Image)| match a.0.cmp(&b.0) { Ordering::Equal => a.1.cmp(&b.1), x => x });
let data = images.into_iter()
.fold(Vec::new(), |mut list: Vec<(String, AtlasItem)>, item: (String, i32, Image)| {
let len = list.len();
if len == 0 || list[len - 1].0 != item.0 {
list.push((item.0, AtlasItem::Image(item.2)));
} else {
let is_still = match list[len - 1].1 { AtlasItem::Image(_) => true, _ => false };
if is_still {
let image = match list[len - 1].1 {
AtlasItem::Image(ref img) => img.clone(),
_ => unreachable!()
};
list[len - 1] = (item.0, AtlasItem::Animation(vec![image]));
}
match list[len - 1].1 {
AtlasItem::Animation(ref mut vec) => vec.push(item.2),
_ => unreachable!()
}
}
list
}).into_iter().collect::<HashMap<String, AtlasItem>>();
Atlas { data }
}
#[derive(Clone, Debug)]
pub enum AtlasItem {
Image(Image),
Animation(Vec<Image>)
}
impl AtlasItem {
pub fn unwrap_image(self) -> Image {
match self {
AtlasItem::Image(image) => image,
AtlasItem::Animation(_) => panic!("called `AtlasItem::unwrap_image` on an Animation")
}
}
pub fn unwrap_animation(self) -> Vec<Image> {
match self {
AtlasItem::Animation(anim) => anim,
AtlasItem::Image(_) => panic!("called `AtlasItem::unwrap_animation` on an Image")
}
}
}
#[derive(Debug)]
pub enum AtlasError {
ImageError(ImageError),
ParseError(&'static str),
IOError(IOError)
}
impl Display for AtlasError {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", self.description())
}
}
impl Error for AtlasError {
fn description(&self) -> &str {
match self {
&AtlasError::ImageError(ref err) => err.description(),
&AtlasError::ParseError(string) => string,
&AtlasError::IOError(ref err) => err.description()
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
&AtlasError::ImageError(ref err) => Some(err),
&AtlasError::ParseError(_) => None,
&AtlasError::IOError(ref err) => Some(err)
}
}
}
#[doc(hidden)]
impl From<ImageError> for AtlasError {
fn from(err: ImageError) -> AtlasError {
AtlasError::ImageError(err)
}
}
#[doc(hidden)]
impl From<ParseIntError> for AtlasError {
fn from(_: ParseIntError) -> AtlasError {
AtlasError::ParseError("Failed to parse an int")
}
}
#[doc(hidden)]
impl From<ParseBoolError> for AtlasError {
fn from(_: ParseBoolError) -> AtlasError {
AtlasError::ParseError("Failed to parse an bool")
}
}
#[doc(hidden)]
impl From<IOError> for AtlasError {
fn from(err: IOError) -> AtlasError {
AtlasError::IOError(err)
}
}