use bevy::asset::{AssetLoader, LoadContext, io::Reader};
use bevy::prelude::*;
#[derive(Asset, TypePath, Debug, Clone)]
pub struct XamlAsset {
pub source: String,
pub base_dir: String,
pub merged: std::sync::Arc<bevy::platform::collections::HashMap<String, String>>,
}
impl XamlAsset {
pub fn document(&self) -> bevy_pf_xaml::XamlDocument {
bevy_pf_xaml::parse(&self.source).expect("XamlAsset was validated at load time")
}
pub fn env(&self) -> crate::instantiate::XamlEnv {
let merged = self.merged.clone();
crate::instantiate::XamlEnv {
base_dir: self.base_dir.clone(),
loader: Some(std::sync::Arc::new(move |assembly, path| {
if assembly.is_some() {
return None; }
merged.get(path).cloned()
})),
}
}
}
fn collect_rd_sources(node: &bevy_pf_xaml::XamlNode, out: &mut Vec<String>) {
if node.name == "ResourceDictionary"
&& let Some(bevy_pf_xaml::XamlValue::Str(src)) = node.attribute("Source")
{
out.push(src.clone());
}
for pe in &node.property_elements {
for el in pe.elements() {
collect_rd_sources(el, out);
}
}
for el in node.child_elements() {
collect_rd_sources(el, out);
}
}
#[derive(Debug, thiserror::Error)]
pub enum XamlAssetError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("utf-8 error: {0}")]
Utf8(#[from] std::string::FromUtf8Error),
#[error("xaml error: {0}")]
Xaml(#[from] bevy_pf_xaml::XamlError),
}
#[derive(Default, TypePath)]
pub struct XamlAssetLoader;
impl AssetLoader for XamlAssetLoader {
type Asset = XamlAsset;
type Settings = ();
type Error = XamlAssetError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let source = String::from_utf8(bytes)?;
let doc = bevy_pf_xaml::parse(&source)?;
let base_dir = load_context
.path()
.parent()
.map(|p| p.to_string().replace('\\', "/"))
.unwrap_or_default();
let mut merged = bevy::platform::collections::HashMap::default();
let mut visited = std::collections::HashSet::new();
let mut work: Vec<(String, String)> = Vec::new(); let mut roots = Vec::new();
collect_rd_sources(&doc.root, &mut roots);
for src in roots {
work.push((base_dir.clone(), src));
}
while let Some((base, src)) = work.pop() {
let Ok(uri) = bevy_pf_xaml::uri::PfUri::parse(&src) else {
continue; };
if uri.assembly.is_some() {
continue;
}
let resolved = uri.resolve(&base);
if !visited.insert(resolved.clone()) {
continue;
}
let Ok(dep_bytes) = load_context.read_asset_bytes(resolved.clone()).await else {
continue; };
let Ok(text) = String::from_utf8(dep_bytes) else {
continue;
};
if let Ok(dep_doc) = bevy_pf_xaml::parse(&text) {
let sub_base = resolved
.rsplit_once('/')
.map(|(d, _)| d.to_string())
.unwrap_or_default();
let mut subs = Vec::new();
collect_rd_sources(&dep_doc.root, &mut subs);
for s in subs {
work.push((sub_base.clone(), s));
}
}
merged.insert(resolved, text);
}
Ok(XamlAsset {
source,
base_dir,
merged: std::sync::Arc::new(merged),
})
}
fn extensions(&self) -> &[&str] {
&["xaml", "axaml"]
}
}
#[derive(Component, Debug, Clone)]
pub struct XamlView(pub Handle<XamlAsset>);
#[derive(Resource, Default)]
pub(crate) struct PendingXamlViews(Vec<Entity>);
pub(crate) fn queue_xaml_views(
mut events: MessageReader<AssetEvent<XamlAsset>>,
assets: Res<Assets<XamlAsset>>,
views: Query<(Entity, &XamlView)>,
added: Query<(Entity, &XamlView), Added<XamlView>>,
mut pending: ResMut<PendingXamlViews>,
) {
for (entity, view) in &added {
if assets.contains(&view.0) {
pending.0.push(entity);
}
}
for event in events.read() {
let (AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id }) = event
else {
continue;
};
for (entity, view) in &views {
if view.0.id() == *id && !pending.0.contains(&entity) {
pending.0.push(entity);
}
}
}
}
pub(crate) fn apply_xaml_views(world: &mut World) {
let pending = std::mem::take(&mut world.resource_mut::<PendingXamlViews>().0);
for entity in pending {
let Ok(entity_ref) = world.get_entity(entity) else {
continue; };
let Some(view) = entity_ref.get::<XamlView>() else {
continue;
};
let handle = view.0.clone();
let Some((doc, env)) = world
.resource::<Assets<XamlAsset>>()
.get(&handle)
.map(|a| (a.document(), a.env()))
else {
continue;
};
let mut root_entity = world.entity_mut(entity);
root_entity.despawn_children();
root_entity.remove::<(
crate::dynamic::PfDynamicResources,
crate::app_theme::PfAppThemeRefs,
crate::dynamic::PfResources,
crate::binding::PfBindings,
crate::components::PfAttachedProps,
crate::components::XamlNames,
)>();
match crate::instantiate::instantiate_document_env(world, entity, &doc, &env) {
Ok(result) => {
for w in &result.warnings {
warn!("bevy_pf: {w}");
}
world.entity_mut(entity).insert(XamlView(handle));
}
Err(e) => error!("bevy_pf: failed to instantiate XAML view: {e}"),
}
}
}