use bevy::prelude::*;
use bevy::ui::widget::NodeImageMode;
use crate::element::{Attribute, Element};
use crate::ext::ElementFlags;
use crate::protocol::animatable::AnimatableField;
use crate::protocol::background_image::{AtlasSpec, ImageMode, SourceRect};
use crate::style::props::OPACITY;
use crate::style::{Codec, Invalidation, Writer, WriterCtx, owns};
use crate::ui_map::{AtlasLayoutCache, apply_atlas, image_node, svg_image_node};
pub static SRC: Attribute<String> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::new("src")
};
pub static TINT: Attribute<String> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::new("tint")
};
pub static FLIP_X: Attribute<bool> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::new("flipX")
};
pub static FLIP_Y: Attribute<bool> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::new("flipY")
};
pub static IMAGE_MODE: Attribute<ImageMode> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::with_codec("imageMode", Codec::serde_as("ImageMode"))
};
pub static SOURCE_RECT: Attribute<SourceRect> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::with_codec("sourceRect", Codec::serde_as("SourceRect"))
};
pub static ATLAS: Attribute<AtlasSpec> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::with_codec("atlas", Codec::serde_as("AtlasSpec"))
};
pub static VISUAL_BOX: Attribute<String> = Attribute {
invalidate: Invalidation::PAINT,
..Attribute::with_codec(
"visualBox",
Codec::serde_as("\"content\" | \"padding\" | \"border\""),
)
};
pub static IMAGE: Element = Element {
flags: ElementFlags::OWNS_IMAGE,
attrs: &[
&SRC,
&TINT,
&FLIP_X,
&FLIP_Y,
&IMAGE_MODE,
&SOURCE_RECT,
&ATLAS,
&VISUAL_BOX,
],
writers: &[&IMAGE_WRITER],
..Element::new("image")
};
pub static IMAGE_WRITER: Writer = Writer {
reads: &[&OPACITY],
attrs: &[
&SRC,
&TINT,
&FLIP_X,
&FLIP_Y,
&IMAGE_MODE,
&SOURCE_RECT,
&ATLAS,
&VISUAL_BOX,
],
writes: &[owns::<ImageNode>],
apply: apply_image,
};
fn apply_image(ctx: &WriterCtx, s: &crate::style::Style, ec: &mut EntityCommands) {
let opacity = s.get(&OPACITY).static_val();
let attrs = ctx.attrs;
if let Some(path) = attrs
.get(&SRC)
.filter(|p| crate::svg::is_svg_src(p.as_str()))
{
crate::svg::warn_ignored_attrs(attrs.contains(&ATLAS), attrs.contains(&SOURCE_RECT));
let img = svg_image_node(attrs, opacity, ctx.promoted);
let path = path.clone();
ec.queue(move |entity: EntityWorldMut| crate::svg::ensure_svg_image(entity, path, img));
return;
}
let mut img = image_node(attrs, opacity, ctx.assets, ctx.promoted);
let atlas = attrs.get(&ATLAS).cloned();
let fresh = ctx.fresh;
ec.queue(move |mut entity: EntityWorldMut| {
if let Some(atlas) = atlas {
entity.world_scope(|world| {
world.resource_scope(|world, mut cache: Mut<AtlasLayoutCache>| {
let mut layouts = world.resource_mut::<Assets<TextureAtlasLayout>>();
apply_atlas(&mut img, Some(&atlas), &mut layouts, &mut cache);
});
});
}
set_image_node(&mut entity, img);
if !fresh {
entity.remove::<(crate::svg::SvgSurface, crate::ext::LiveTexture)>();
}
});
}
fn set_image_node(entity: &mut EntityWorldMut, img: ImageNode) {
let Some(mut current) = entity.get_mut::<ImageNode>() else {
entity.insert(img);
return;
};
let unchanged = current.image == img.image
&& current.color == img.color
&& current.flip_x == img.flip_x
&& current.flip_y == img.flip_y
&& current.rect == img.rect
&& current.texture_atlas == img.texture_atlas
&& current.visual_box == img.visual_box
&& same_mode(¤t.image_mode, &img.image_mode);
if !unchanged {
*current = img;
}
}
fn same_mode(a: &NodeImageMode, b: &NodeImageMode) -> bool {
format!("{a:?}") == format!("{b:?}")
}