use bevy_app::prelude::*;
use bevy_asset::{io::Reader, prelude::*, AssetLoader, LoadContext, RenderAssetUsages};
use bevy_image::{Image, TextureAtlasBuilder, TextureAtlasBuilderError, TextureAtlasLayout};
use bevy_reflect::prelude::*;
use ico::ResourceType;
use image::{DynamicImage, ImageBuffer};
use thiserror::Error;
use crate::{
cur::decoder::{DecodeError, Decoder},
hotspot::CursorHotspots,
};
#[cfg(feature = "serde_json_asset")]
use super::serde_asset::JsonDeserializer;
#[cfg(feature = "serde_ron_asset")]
use super::serde_asset::RonDeserializer;
#[cfg(feature = "serde_asset")]
use super::serde_asset::SerdeStaticCursorAssetPlugin;
#[cfg(feature = "serde_toml_asset")]
use super::serde_asset::TomlDeserializer;
pub struct StaticCursorAssetPlugin;
impl Plugin for StaticCursorAssetPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "serde_asset")]
{
#[cfg(feature = "serde_json_asset")]
if !app.is_plugin_added::<SerdeStaticCursorAssetPlugin<JsonDeserializer>>() {
app.add_plugins(SerdeStaticCursorAssetPlugin::<JsonDeserializer>::new(
["CUR.json", "cur.json"].to_vec(),
));
}
#[cfg(feature = "serde_ron_asset")]
if !app.is_plugin_added::<SerdeStaticCursorAssetPlugin<RonDeserializer>>() {
app.add_plugins(SerdeStaticCursorAssetPlugin::<RonDeserializer>::new(
["CUR.ron", "cur.ron"].to_vec(),
));
}
#[cfg(feature = "serde_toml_asset")]
if !app.is_plugin_added::<SerdeStaticCursorAssetPlugin<TomlDeserializer>>() {
app.add_plugins(SerdeStaticCursorAssetPlugin::<TomlDeserializer>::new(
["CUR.toml", "cur.toml"].to_vec(),
));
}
}
app.init_asset::<StaticCursor>()
.init_asset_loader::<StaticCursorLoader>()
.register_asset_reflect::<StaticCursor>();
}
}
#[derive(Asset, Clone, Debug, Reflect)]
#[reflect(Debug)]
pub struct StaticCursor {
pub image: Handle<Image>,
pub texture_atlas_layout: Handle<TextureAtlasLayout>,
pub hotspots: CursorHotspots,
}
impl StaticCursor {
#[inline(always)]
pub fn hotspot_or_default(&self, index: usize) -> (u16, u16) {
self.hotspots.get_or_default(index)
}
}
#[derive(Clone, Debug, Default, Reflect)]
#[reflect(Debug, Default)]
pub struct StaticCursorLoader;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum StaticCursorLoaderError {
#[error("could not load asset: {0}")]
Io(#[from] std::io::Error),
#[error("could not decode static cursor: {0}")]
DecodeError(#[from] DecodeError),
#[error("resource type must be cursor, found: {0}")]
InvalidResourceType(String),
#[error("missing hotspot")]
MissingHotspot,
#[error("could not create image buffer")]
ImageBufferError,
#[error("could not build texture atlas: {0}")]
TextureAtlasBuilderError(#[from] TextureAtlasBuilderError),
}
impl AssetLoader for StaticCursorLoader {
type Asset = StaticCursor;
type Settings = ();
type Error = StaticCursorLoaderError;
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 reader = std::io::Cursor::new(bytes);
let mut decoder = Decoder::new(reader);
let c = decoder.decode()?;
let items =
c.0.entries()
.iter()
.enumerate()
.map(|(i, e)| {
if e.resource_type() != ResourceType::Cursor {
return Err(StaticCursorLoaderError::InvalidResourceType(format!(
"{:?}",
e.resource_type()
)));
}
let icon_image = e.decode()?;
let image = ImageBuffer::from_raw(
icon_image.width(),
icon_image.height(),
icon_image.rgba_data().to_vec(),
)
.map(DynamicImage::ImageRgba8)
.ok_or(StaticCursorLoaderError::ImageBufferError)?;
let image = Image::from_dynamic(image, true, RenderAssetUsages::MAIN_WORLD);
let hotspot = icon_image
.cursor_hotspot()
.ok_or(StaticCursorLoaderError::MissingHotspot)?;
Ok((
(
load_context.labeled_asset_scope(
format!("image_{}", i).to_string(),
|_| -> Result<Image, StaticCursorLoaderError> { Ok(image.clone()) },
)?,
image,
),
hotspot,
))
})
.collect::<Result<Vec<_>, StaticCursorLoaderError>>()?;
let mut texture_atlas_builder = TextureAtlasBuilder::default();
let mut hotspots = Vec::new();
for ((handle, image), hotspot) in items.iter() {
texture_atlas_builder.add_texture(Some(handle.id()), image);
hotspots.push(*hotspot);
}
let (texture_atlas_layout, _, image) = texture_atlas_builder.build()?;
let texture_atlas_layout = load_context.labeled_asset_scope(
"texture_atlas_layout".to_string(),
|_| -> Result<TextureAtlasLayout, StaticCursorLoaderError> { Ok(texture_atlas_layout) },
)?;
let image = load_context.labeled_asset_scope(
"image".to_string(),
|_| -> Result<Image, StaticCursorLoaderError> { Ok(image) },
)?;
let hotspots = CursorHotspots {
overrides: hotspots
.iter()
.enumerate()
.map(|(i, hotspot)| (i, *hotspot))
.collect(),
..Default::default()
};
Ok(StaticCursor {
image,
texture_atlas_layout,
hotspots,
})
}
fn extensions(&self) -> &[&str] {
&["CUR", "cur"]
}
}