use bevy::asset::{AssetEvent, AssetId, Assets};
use bevy::ecs::change_detection::Ref;
use bevy::ecs::entity::EntityHashSet;
use bevy::prelude::*;
use bevy::render::render_resource::Extent3d;
use bevy::ui::widget::ImageNode;
use bevy::ui::{ComputedNode, ComputedUiRenderTargetInfo, ContentSize};
use super::walk::{ShapeQuery, climb_to_svg_root, walk_shapes};
use super::{SvgDocument, SvgShape, SvgSurface, stamp_intrinsic_measure};
pub fn rasterize_document(doc: &SvgDocument, w: u32, h: u32) -> Option<tiny_skia::Pixmap> {
let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
if doc.size.x <= 0.0 || doc.size.y <= 0.0 {
return Some(pixmap); }
let transform = super::paint::meet_transform(Vec2::ZERO, doc.size, w, h);
resvg::render(&doc.tree, transform, &mut pixmap.as_mut());
Some(pixmap)
}
fn touched_docs(events: &mut MessageReader<AssetEvent<SvgDocument>>) -> Vec<AssetId<SvgDocument>> {
let mut touched = Vec::new();
for event in events.read() {
if let AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } = event {
touched.push(*id);
}
}
touched
}
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub fn update_svg_surfaces(
mut images: ResMut<Assets<Image>>,
docs: Res<Assets<SvgDocument>>,
mut doc_events: MessageReader<AssetEvent<SvgDocument>>,
mut dirt: ResMut<crate::layer::LayerContentDirt>,
mut query: Query<(
Entity,
&ComputedNode,
&ImageNode,
Option<&Children>,
&mut SvgSurface,
&mut ContentSize,
)>,
shapes: ShapeQuery,
changed_shapes: Query<Entity, Changed<SvgShape>>,
changed_children: Query<Entity, (Changed<Children>, Or<(With<SvgShape>, With<SvgSurface>)>)>,
mut removed_children: RemovedComponents<Children>,
parents: Query<&ChildOf>,
svg_roots: Query<(), With<SvgSurface>>,
) {
let mut jsx_dirty = EntityHashSet::default();
for entity in &changed_shapes {
if let Some(root) = climb_to_svg_root(entity, &parents, &svg_roots) {
jsx_dirty.insert(root);
}
}
for entity in &changed_children {
if svg_roots.contains(entity) {
jsx_dirty.insert(entity);
} else if let Some(root) = climb_to_svg_root(entity, &parents, &svg_roots) {
jsx_dirty.insert(root);
}
}
for entity in removed_children.read() {
if svg_roots.contains(entity) {
jsx_dirty.insert(entity);
} else if shapes.contains(entity)
&& let Some(root) = climb_to_svg_root(entity, &parents, &svg_roots)
{
jsx_dirty.insert(root);
}
}
let touched = touched_docs(&mut doc_events);
for (entity, node, image_node, children, mut surface, mut content_size) in &mut query {
let Some(doc_handle) = surface.doc.as_ref() else {
raster_jsx_surface(
entity,
node,
image_node,
children,
&mut surface,
jsx_dirty.contains(&entity),
&shapes,
&mut images,
&mut dirt,
);
continue;
};
let Some(doc) = docs.get(doc_handle) else {
continue; };
let doc_touched = touched.contains(&doc_handle.id());
let (w, h) = crate::canvas::clamp_physical_size(node.size);
if w == 0 || h == 0 {
if doc_touched && !surface.dirty {
surface.dirty = true;
}
continue;
}
let size = UVec2::new(w, h);
if !surface.dirty && surface.last_size == size && !doc_touched {
continue; }
if !images.contains(&image_node.image) {
continue;
}
let Some(pixmap) = rasterize_document(doc, w, h) else {
continue;
};
upload_pixmap(entity, image_node, &mut images, &mut dirt, w, h, &pixmap);
if surface.last_size == UVec2::ZERO {
let scale_factor = if node.inverse_scale_factor > 0.0 {
node.inverse_scale_factor.recip()
} else {
1.0
};
stamp_intrinsic_measure(
&mut content_size,
doc.size,
scale_factor,
image_node.visual_box,
);
}
if surface.last_size != size {
surface.last_size = size;
}
if surface.dirty {
surface.dirty = false;
}
}
}
#[allow(clippy::too_many_arguments)] fn raster_jsx_surface(
entity: Entity,
node: &ComputedNode,
image_node: &ImageNode,
children: Option<&Children>,
surface: &mut Mut<SvgSurface>,
derived_dirty: bool,
shapes: &ShapeQuery,
images: &mut Assets<Image>,
dirt: &mut crate::layer::LayerContentDirt,
) {
let (w, h) = crate::canvas::clamp_physical_size(node.size);
if w == 0 || h == 0 {
if derived_dirty && !surface.dirty {
surface.dirty = true;
}
return;
}
let size = UVec2::new(w, h);
if !surface.dirty && surface.last_size == size && !derived_dirty {
return; }
if !images.contains(&image_node.image) {
return;
}
let Some(mut pixmap) = tiny_skia::Pixmap::new(w, h) else {
return;
};
let scale_factor = if node.inverse_scale_factor > 0.0 {
node.inverse_scale_factor.recip()
} else {
1.0
};
let transform = super::paint::view_box_transform(surface.view_box.as_ref(), w, h, scale_factor);
if let Some(children) = children {
walk_shapes(children, shapes, transform, 1.0, &mut |_, shape, t, o| {
super::paint::paint_shape(&mut pixmap, shape.kind, &shape.attrs, t, o);
});
}
upload_pixmap(entity, image_node, images, dirt, w, h, &pixmap);
if surface.last_size != size {
surface.last_size = size;
}
if surface.dirty {
surface.dirty = false;
}
}
fn upload_pixmap(
entity: Entity,
image_node: &ImageNode,
images: &mut Assets<Image>,
dirt: &mut crate::layer::LayerContentDirt,
w: u32,
h: u32,
pixmap: &tiny_skia::Pixmap,
) {
dirt.nodes.push(entity);
let Some(mut image) = images.get_mut(&image_node.image) else {
return;
};
let extent = Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
};
if image.texture_descriptor.size != extent {
image.resize(extent);
}
image.data = Some(crate::canvas::to_straight_alpha(pixmap));
}
pub fn stamp_svg_measures(
docs: Res<Assets<SvgDocument>>,
mut doc_events: MessageReader<AssetEvent<SvgDocument>>,
mut query: Query<(
Ref<ImageNode>,
&SvgSurface,
&mut ContentSize,
Ref<ComputedUiRenderTargetInfo>,
)>,
) {
let touched = touched_docs(&mut doc_events);
for (image, surface, mut content_size, target) in &mut query {
let Some(doc_handle) = surface.doc.as_ref() else {
continue; };
if !image.is_changed() && !target.is_changed() && !touched.contains(&doc_handle.id()) {
continue;
}
let Some(doc) = docs.get(doc_handle) else {
continue; };
let sf = target.scale_factor();
let scale_factor = if sf > 0.0 { sf } else { 1.0 };
stamp_intrinsic_measure(&mut content_size, doc.size, scale_factor, image.visual_box);
}
}
#[cfg(test)]
mod tests;