You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
4.8 KiB
153 lines
4.8 KiB
use crate::{error::LoadError, GameSettings};
|
|
use cairo::{Context, Format, ImageSurface, Rectangle};
|
|
use ggez::graphics::Image;
|
|
use librsvg::{CairoRenderer, Loader};
|
|
use std::{
|
|
collections::BTreeMap,
|
|
ffi::OsStr,
|
|
fs::{read_dir, File},
|
|
io::BufWriter,
|
|
io::Read,
|
|
path::{Path, PathBuf},
|
|
};
|
|
use tempfile::tempdir;
|
|
|
|
pub use rst_core::loader::Uri;
|
|
|
|
pub type Result<T> = std::result::Result<T, LoadError>;
|
|
|
|
/// Asset loader
|
|
#[derive(Debug)]
|
|
pub struct Assets {
|
|
inner: BTreeMap<Uri, Image>,
|
|
}
|
|
|
|
impl Assets {
|
|
fn new() -> Self {
|
|
Self {
|
|
inner: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn find<U: Into<Uri>>(&self, u: U) -> Option<Image> {
|
|
self.inner.get(&u.into()).map(|i| i.clone())
|
|
}
|
|
|
|
/// Load an asset directory path
|
|
fn load_tree(&mut self, ctx: &mut ggez::Context, tmpdir: &Path, p: &Path) -> Result<()> {
|
|
let err: LoadError = p.to_str().unwrap().into();
|
|
|
|
read_dir(p)
|
|
.map_err(|_| err)?
|
|
.map(|e| {
|
|
let e = e.unwrap();
|
|
let p = e.path();
|
|
|
|
let ext = OsStr::new("svg");
|
|
|
|
if p.is_dir() {
|
|
debug!(
|
|
"Entering directory {}",
|
|
p.file_name().unwrap().to_str().unwrap()
|
|
);
|
|
self.load_tree(ctx, tmpdir, p.as_path())?;
|
|
} else if p.extension() == Some(ext) {
|
|
let png = load_svg(tmpdir, p.as_path())?;
|
|
|
|
let basepath = p.with_extension("");
|
|
|
|
let uri_cat = p.parent().unwrap().file_name().unwrap().to_str().unwrap();
|
|
let name = basepath.file_name().unwrap().to_str().unwrap();
|
|
let uri = format!("{}/{}", uri_cat, name);
|
|
let path_str = png.as_path().to_str().unwrap();
|
|
|
|
let mut content = vec![];
|
|
let mut f = File::open(png.as_path()).map_err(|_| {
|
|
LoadError::from(format!("No such file: {}", path_str).as_str())
|
|
})?;
|
|
|
|
f.read_to_end(&mut content).map_err(|e| {
|
|
LoadError::from(
|
|
format!("Read error for {}: {}", path_str, e.to_string()).as_str(),
|
|
)
|
|
})?;
|
|
|
|
self.inner.insert(
|
|
uri.into(),
|
|
Image::from_bytes(ctx, content.as_slice()).map_err(|e| {
|
|
LoadError::from(
|
|
format!("Read error for {}: {}", path_str, e.to_string()).as_str(),
|
|
)
|
|
})?,
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.fold(Ok(()), |acc, res| match (acc, res) {
|
|
(Ok(_), Ok(_)) => Ok(()),
|
|
(Ok(_), Err(e)) => Err(e),
|
|
(Err(e), _) => Err(e),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Load all game assets into the game
|
|
///
|
|
/// This function performs three main steps.
|
|
///
|
|
/// 1. Check that the provided path is a directory
|
|
/// 2. Recursively load Directories and files and call
|
|
/// [`load_svg`](self::load_svg) on each `.svg` file
|
|
/// 3. Re-load newly converted assets into [`Assets`](self::Assets)
|
|
pub fn load_tree(ctx: &mut ggez::Context, settings: &GameSettings) -> Result<Assets> {
|
|
let path = match settings.assets.clone() {
|
|
Some(s) => Ok(s),
|
|
None => Err(LoadError::from("No assets path set!")),
|
|
}?;
|
|
|
|
debug!(
|
|
"Starting assets loading harness on {}",
|
|
path.to_str().unwrap()
|
|
);
|
|
|
|
let tmpdir = tempdir().unwrap();
|
|
let mut assets = Assets::new();
|
|
assets.load_tree(ctx, tmpdir.path(), path.as_path())?;
|
|
info!("Asset loading complete!");
|
|
Ok(assets)
|
|
}
|
|
|
|
/// A utility function to take an SVG and render it to a raster image
|
|
/// according to a render spec
|
|
pub fn load_svg(tmpdir: &Path, p: &Path) -> Result<PathBuf> {
|
|
let err: LoadError = p.to_str().unwrap().into();
|
|
|
|
let handle = Loader::new().read_path(p).map_err(|_| err.clone())?;
|
|
let renderer = CairoRenderer::new(&handle);
|
|
|
|
let surf = ImageSurface::create(Format::ARgb32, 256, 256).map_err(|_| err.clone())?;
|
|
let cr = Context::new(&surf);
|
|
|
|
renderer
|
|
.render_document(
|
|
&cr,
|
|
&Rectangle {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
width: 256.0,
|
|
height: 256.0,
|
|
},
|
|
)
|
|
.map_err(|_| err.clone())?;
|
|
|
|
let png = p.with_extension("png");
|
|
let name = png
|
|
.file_name()
|
|
.map_or_else(|| Err(err.clone()), |name| Ok(name))?;
|
|
|
|
let out = tmpdir.join(name.clone());
|
|
let mut file = BufWriter::new(File::create(out.clone()).map_err(|_| err.clone())?);
|
|
surf.write_to_png(&mut file).map_err(|_| err.clone())?;
|
|
Ok(out.to_path_buf())
|
|
}
|
|
|