use crate::{ComputedUiRenderTargetInfo, ContentSize, Measure, MeasureArgs, Node, NodeMeasure};
use bevy_asset::{AsAssetId, AssetId, Assets, Handle};
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::{prelude::*, TRANSPARENT_IMAGE_HANDLE};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_sprite::TextureSlicer;
use taffy::{MaybeMath, MaybeResolve};
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
#[require(Node, ImageNodeSize, ContentSize)]
pub struct ImageNode {
pub color: Color,
pub image: Handle<Image>,
pub texture_atlas: Option<TextureAtlas>,
pub flip_x: bool,
pub flip_y: bool,
pub rect: Option<Rect>,
pub image_mode: NodeImageMode,
}
impl Default for ImageNode {
fn default() -> Self {
ImageNode {
color: Color::WHITE,
texture_atlas: None,
image: TRANSPARENT_IMAGE_HANDLE,
flip_x: false,
flip_y: false,
rect: None,
image_mode: NodeImageMode::Auto,
}
}
}
impl ImageNode {
pub fn new(texture: Handle<Image>) -> Self {
Self {
image: texture,
color: Color::WHITE,
..Default::default()
}
}
pub fn solid_color(color: Color) -> Self {
Self {
image: Handle::default(),
color,
flip_x: false,
flip_y: false,
texture_atlas: None,
rect: None,
image_mode: NodeImageMode::Auto,
}
}
pub fn from_atlas_image(image: Handle<Image>, atlas: TextureAtlas) -> Self {
Self {
image,
texture_atlas: Some(atlas),
..Default::default()
}
}
#[must_use]
pub const fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
#[must_use]
pub const fn with_flip_x(mut self) -> Self {
self.flip_x = true;
self
}
#[must_use]
pub const fn with_flip_y(mut self) -> Self {
self.flip_y = true;
self
}
#[must_use]
pub const fn with_rect(mut self, rect: Rect) -> Self {
self.rect = Some(rect);
self
}
#[must_use]
pub const fn with_mode(mut self, mode: NodeImageMode) -> Self {
self.image_mode = mode;
self
}
}
impl From<Handle<Image>> for ImageNode {
fn from(texture: Handle<Image>) -> Self {
Self::new(texture)
}
}
impl AsAssetId for ImageNode {
type Asset = Image;
fn as_asset_id(&self) -> AssetId<Self::Asset> {
self.image.id()
}
}
#[derive(Default, Debug, Clone, PartialEq, Reflect)]
#[reflect(Clone, Default, PartialEq)]
pub enum NodeImageMode {
#[default]
Auto,
Stretch,
Sliced(TextureSlicer),
Tiled {
tile_x: bool,
tile_y: bool,
stretch_value: f32,
},
}
impl NodeImageMode {
#[inline]
pub const fn uses_slices(&self) -> bool {
matches!(
self,
NodeImageMode::Sliced(..) | NodeImageMode::Tiled { .. }
)
}
}
#[derive(Component, Debug, Copy, Clone, Default, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct ImageNodeSize {
size: UVec2,
}
impl ImageNodeSize {
#[inline]
pub const fn size(&self) -> UVec2 {
self.size
}
}
#[derive(Clone)]
pub struct ImageMeasure {
pub size: Vec2,
}
impl Measure for ImageMeasure {
fn measure(&mut self, measure_args: MeasureArgs, style: &taffy::Style) -> Vec2 {
let MeasureArgs {
width,
height,
available_width,
available_height,
..
} = measure_args;
let parent_width = available_width.into_option();
let parent_height = available_height.into_option();
let s_aspect_ratio = style.aspect_ratio;
let s_width = style.size.width.maybe_resolve(parent_width);
let s_min_width = style.min_size.width.maybe_resolve(parent_width);
let s_max_width = style.max_size.width.maybe_resolve(parent_width);
let s_height = style.size.height.maybe_resolve(parent_height);
let s_min_height = style.min_size.height.maybe_resolve(parent_height);
let s_max_height = style.max_size.height.maybe_resolve(parent_height);
let width = width.or(s_width
.or(s_min_width)
.maybe_clamp(s_min_width, s_max_width));
let height = height.or(s_height
.or(s_min_height)
.maybe_clamp(s_min_height, s_max_height));
let aspect_ratio = s_aspect_ratio.unwrap_or_else(|| self.size.x / self.size.y);
let taffy_size = taffy::Size { width, height }.maybe_apply_aspect_ratio(Some(aspect_ratio));
Vec2 {
x: taffy_size
.width
.unwrap_or(self.size.x)
.maybe_clamp(s_min_width, s_max_width),
y: taffy_size
.height
.unwrap_or(self.size.y)
.maybe_clamp(s_min_height, s_max_height),
}
}
}
type UpdateImageFilter = (With<Node>, Without<crate::prelude::Text>);
pub fn update_image_content_size_system(
textures: Res<Assets<Image>>,
atlases: Res<Assets<TextureAtlasLayout>>,
mut query: Query<
(
&mut ContentSize,
Ref<ImageNode>,
&mut ImageNodeSize,
Ref<ComputedUiRenderTargetInfo>,
),
UpdateImageFilter,
>,
) {
for (mut content_size, image, mut image_size, computed_target) in &mut query {
if !matches!(image.image_mode, NodeImageMode::Auto)
|| image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
{
if image.is_changed() {
content_size.measure = None;
}
continue;
}
if let Some(size) =
image
.rect
.map(|rect| rect.size().as_uvec2())
.or_else(|| match &image.texture_atlas {
Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()),
None => textures.get(&image.image).map(Image::size),
})
{
if size != image_size.size || computed_target.is_changed() || content_size.is_added() {
image_size.size = size;
content_size.set(NodeMeasure::Image(ImageMeasure {
size: size.as_vec2() * computed_target.scale_factor(),
}));
}
}
}
}