use std::hash::{Hash, Hasher};
use bevy::{
asset::{io::Reader, AssetLoader, AsyncReadExt},
prelude::Asset,
reflect::TypePath,
utils::{AHasher, HashMap},
};
use smallvec::SmallVec;
use thiserror::Error;
use crate::{parser::StyleSheetParser, property::PropertyValues, selector::Selector};
#[derive(Debug, TypePath, Asset)]
pub struct StyleSheetAsset {
path: String,
hash: u64,
rules: SmallVec<[StyleRule; 8]>,
}
impl StyleSheetAsset {
pub fn parse(path: &str, content: &str) -> Self {
let mut hasher = AHasher::default();
content.hash(&mut hasher);
let hash = hasher.finish();
Self {
path: path.to_string(),
hash,
rules: StyleSheetParser::parse(content),
}
}
pub fn get_properties(&self, selector: &Selector, name: &str) -> Option<&PropertyValues> {
self.rules
.iter()
.find(|&rule| &rule.selector == selector)
.and_then(|rule| rule.properties.get(name))
}
pub fn iter(&self) -> impl Iterator<Item = &StyleRule> {
self.rules.iter()
}
pub fn hash(&self) -> u64 {
self.hash
}
pub fn path(&self) -> &str {
&self.path
}
}
#[derive(Debug, Clone)]
pub struct StyleRule {
pub selector: Selector,
pub properties: HashMap<String, PropertyValues>,
}
#[derive(Default)]
pub(crate) struct StyleSheetLoader;
#[derive(Debug, Error)]
pub enum StyleSheetLoaderError {
#[error("File not found: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid file format: {0}")]
UTF8(#[from] std::str::Utf8Error),
}
impl AssetLoader for StyleSheetLoader {
type Asset = StyleSheetAsset;
type Settings = ();
type Error = StyleSheetLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut bevy::asset::LoadContext,
) -> bevy::utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let content = std::str::from_utf8(&bytes)?;
let stylesheet =
StyleSheetAsset::parse(load_context.path().to_str().unwrap_or_default(), content);
Ok(stylesheet)
})
}
fn extensions(&self) -> &[&str] {
&["css"]
}
}