#[cfg(feature = "serde_toml_asset")]
use std::str::from_utf8;
use std::{fmt::Debug, marker::PhantomData};
use bevy_app::prelude::*;
use bevy_asset::{io::Reader, prelude::*, AssetLoader, LoadContext};
use bevy_image::TextureAtlasLayout;
use bevy_math::UVec2;
use bevy_reflect::prelude::*;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
asset_image::{load_image, LoadImageError},
hotspot::CursorHotspots,
};
use super::asset::StaticCursor;
pub struct SerdeStaticCursorAssetPlugin<D: Deserializer> {
_phantom: PhantomData<D>,
extensions: Vec<&'static str>,
}
impl<D: Deserializer> SerdeStaticCursorAssetPlugin<D> {
pub fn new(extensions: Vec<&'static str>) -> Self {
Self {
_phantom: PhantomData,
extensions,
}
}
}
impl<D: Deserializer + TypePath> Plugin for SerdeStaticCursorAssetPlugin<D> {
fn build(&self, app: &mut App) {
app.register_asset_loader(SerdeStaticCursorLoader::<D>::new(
D::default(),
self.extensions.clone(),
));
}
}
#[derive(Asset, Debug, Clone, Deserialize, Reflect, Serialize)]
#[reflect(Debug, Deserialize, Serialize)]
pub struct SerdeStaticCursor {
pub image: SerdeImage,
pub texture_atlas_layout: SerdeTextureAtlasLayout,
#[serde(default)]
pub hotspots: CursorHotspots,
}
#[derive(Clone, Debug, Default, Deserialize, Reflect, Serialize)]
#[reflect(Debug, Default, Deserialize, Serialize)]
pub struct SerdeImage {
pub path: String,
#[serde(default)]
pub color_key: Option<(u8, u8, u8)>,
#[serde(default)]
pub flip_x: bool,
#[serde(default)]
pub flip_y: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Reflect, Serialize)]
#[reflect(Debug, Default, Deserialize, Serialize)]
pub struct SerdeTextureAtlasLayout {
pub tile_size: UVec2,
pub columns: u32,
pub rows: u32,
pub padding: Option<UVec2>,
pub offset: Option<UVec2>,
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum DeserializeError {
#[cfg(feature = "serde_json_asset")]
#[error("Could not parse the JSON: {0}")]
Json(#[from] serde_json::error::Error),
#[cfg(feature = "serde_ron_asset")]
#[error("could not parse RON: {0}")]
Ron(#[from] ron::error::SpannedError),
#[cfg(feature = "serde_toml_asset")]
#[error("Could not interpret as UTF-8: {0}")]
FormatError(#[from] std::str::Utf8Error),
#[cfg(feature = "serde_toml_asset")]
#[error("Could not parse TOML: {0}")]
Toml(#[from] serde_toml::de::Error),
}
pub trait Deserializer: Debug + Default + Send + Sync + 'static {
fn deserialize(&self, bytes: &[u8]) -> Result<SerdeStaticCursor, DeserializeError>;
}
#[cfg(feature = "serde_json_asset")]
#[derive(Clone, Debug, Default, TypePath)]
pub struct JsonDeserializer;
#[cfg(feature = "serde_json_asset")]
impl Deserializer for JsonDeserializer {
fn deserialize(&self, bytes: &[u8]) -> Result<SerdeStaticCursor, DeserializeError> {
Ok(serde_json::from_slice(bytes)?)
}
}
#[cfg(feature = "serde_ron_asset")]
#[derive(Clone, Debug, Default, TypePath)]
pub struct RonDeserializer;
#[cfg(feature = "serde_ron_asset")]
impl Deserializer for RonDeserializer {
fn deserialize(&self, bytes: &[u8]) -> Result<SerdeStaticCursor, DeserializeError> {
Ok(ron::de::from_bytes::<SerdeStaticCursor>(bytes)?)
}
}
#[cfg(feature = "serde_toml_asset")]
#[derive(Clone, Debug, Default, TypePath)]
pub struct TomlDeserializer;
#[cfg(feature = "serde_toml_asset")]
impl Deserializer for TomlDeserializer {
fn deserialize(&self, bytes: &[u8]) -> Result<SerdeStaticCursor, DeserializeError> {
Ok(serde_toml::from_str::<SerdeStaticCursor>(from_utf8(
bytes,
)?)?)
}
}
#[derive(TypePath)]
pub struct SerdeStaticCursorLoader<D: Deserializer + TypePath> {
_phantom: PhantomData<D>,
extensions: Vec<&'static str>,
deserializer: D,
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum SerdeStaticCursorLoaderError {
#[error("could not load asset: {0}")]
Io(#[from] std::io::Error),
#[error("could not deserialize static cursor: {0}")]
DeserializeError(#[from] DeserializeError),
#[error("could not load image: {0}")]
LoadImageError(#[from] LoadImageError),
}
impl<D: Deserializer + TypePath> AssetLoader for SerdeStaticCursorLoader<D> {
type Asset = StaticCursor;
type Settings = ();
type Error = SerdeStaticCursorLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let c = self.deserializer.deserialize(&bytes)?;
let image = if c.image.color_key.is_some() || c.image.flip_x || c.image.flip_y {
let image = load_image(
load_context,
&c.image.path,
c.image.color_key,
c.image.flip_x,
c.image.flip_y,
)
.await?;
load_context.add_labeled_asset("image".to_string(), image)
} else {
load_context.load(&c.image.path)
};
let texture_atlas_layout = bevy_image::TextureAtlasLayout::from_grid(
c.texture_atlas_layout.tile_size,
c.texture_atlas_layout.columns,
c.texture_atlas_layout.rows,
c.texture_atlas_layout.padding,
c.texture_atlas_layout.offset,
);
let texture_atlas_layout = load_context.labeled_asset_scope(
"texture_atlas_layout".to_string(),
|_| -> Result<TextureAtlasLayout, SerdeStaticCursorLoaderError> {
Ok(texture_atlas_layout)
},
)?;
Ok(StaticCursor {
image,
texture_atlas_layout,
hotspots: c.hotspots,
})
}
fn extensions(&self) -> &[&str] {
&self.extensions
}
}
impl<D: Deserializer + TypePath> SerdeStaticCursorLoader<D> {
pub fn new(deserializer: D, extensions: Vec<&'static str>) -> Self {
Self {
_phantom: PhantomData,
deserializer,
extensions,
}
}
}