use bevy::asset::{AssetEvent, AssetId, Assets};
use bevy::ecs::change_detection::Ref;
use bevy::prelude::*;
use bevy::ui::widget::ImageNode;
use bevy::ui::{ComputedNode, ComputedUiRenderTargetInfo, ContentSize};
use super::{SvgDocument, SvgSurface, stamp_intrinsic_measure};
use crate::animations::AnimatedNode;
use crate::raster::upload_pixmap;
use crate::transition::TransitionState;
const EASE_RERASTER_RATIO: f32 = 0.12;
fn resize_deferred(
state: Option<&TransitionState>,
animated: Option<&AnimatedNode>,
last: UVec2,
size: UVec2,
) -> bool {
let moving = state.is_some_and(TransitionState::size_in_flight)
|| animated.is_some_and(|a| a.0.has_node_props());
if !moving {
return false;
}
let ratio = |a: u32, b: u32| (a as f32 - b as f32).abs() / (b.max(1) as f32);
ratio(size.x, last.x).max(ratio(size.y, last.y)) < EASE_RERASTER_RATIO
}
pub fn rasterize_document(doc: &SvgDocument, w: u32, h: u32) -> Option<tiny_skia::Pixmap> {
let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
render_document(doc, &mut pixmap);
Some(pixmap)
}
fn render_document(doc: &SvgDocument, pixmap: &mut tiny_skia::Pixmap) {
if doc.size.x <= 0.0 || doc.size.y <= 0.0 {
return; }
let transform =
crate::raster::meet_transform(Vec2::ZERO, doc.size, pixmap.width(), pixmap.height());
resvg::render(&doc.tree, transform, &mut pixmap.as_mut());
}
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)]
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,
&mut SvgSurface,
&mut ContentSize,
Option<&TransitionState>,
Option<&AnimatedNode>,
)>,
) {
let touched = touched_docs(&mut doc_events);
for (entity, node, image_node, mut surface, mut content_size, state, animated) in &mut query {
let doc_handle = &surface.doc;
let Some(doc) = docs.get(doc_handle) else {
continue; };
let doc_touched = touched.contains(&doc_handle.id());
let (w, h) = crate::raster::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 !surface.dirty
&& !doc_touched
&& surface.last_size != UVec2::ZERO
&& resize_deferred(state, animated, surface.last_size, size)
{
continue;
}
if !images.contains(&image_node.image) {
continue;
}
let raster = &mut surface.bypass_change_detection().raster;
let Some(mut pixmap) = raster.take_pixmap(w, h) else {
continue;
};
render_document(doc, &mut pixmap);
upload_pixmap(
entity,
image_node,
&mut images,
&mut dirt,
w,
h,
&pixmap,
raster,
);
raster.keep_pixmap(pixmap);
if surface.last_size == UVec2::ZERO {
let scale_factor = crate::raster::node_scale_factor(node);
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;
}
}
}
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 doc_handle = &surface.doc;
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;