bevy/crates/bevy_ui/src/widget/image.rs
mwbryant 8b5bf42c28
UI texture atlas support (#8822)
# Objective

This adds support for using texture atlas sprites in UI. From
discussions today in the ui-dev discord it seems this is a much wanted
feature.

This was previously attempted in #5070 by @ManevilleF however that was
blocked #5103. This work can be easily modified to support #5103 changes
after that merges.

## Solution

I created a new UI bundle that reuses the existing texture atlas
infrastructure. I create a new atlas image component to prevent it from
being drawn by the existing non-UI systems and to remove unused
parameters.

In extract I added new system to calculate the required values for the
texture atlas image, this extracts into the same resource as the
existing UI Image and Text components.

This should have minimal performance impact because if texture atlas is
not present then the exact same code path is followed. Also there should
be no unintended behavior changes because without the new components the
existing systems write the extract same resulting data.

I also added an example showing the sprite working and a system to
advance the animation on space bar presses.

Naming is hard and I would accept any feedback on the bundle name! 

---

## Changelog

>  Added TextureAtlasImageBundle

---------

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
2023-06-19 21:52:02 +00:00

133 lines
3.9 KiB
Rust

use crate::{
measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiTextureAtlasImage,
};
use bevy_asset::{Assets, Handle};
#[cfg(feature = "bevy_text")]
use bevy_ecs::query::Without;
use bevy_ecs::{
prelude::Component,
query::With,
reflect::ReflectComponent,
system::{Query, Res},
};
use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect};
use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
use bevy_text::Text;
/// The size of the image in physical pixels
///
/// This field is set automatically by `update_image_calculated_size_system`
#[derive(Component, Debug, Copy, Clone, Default, Reflect, FromReflect)]
#[reflect(Component, Default, FromReflect)]
pub struct UiImageSize {
size: Vec2,
}
impl UiImageSize {
pub fn size(&self) -> Vec2 {
self.size
}
}
#[derive(Clone)]
pub struct ImageMeasure {
// target size of the image
size: Vec2,
}
impl Measure for ImageMeasure {
fn measure(
&self,
width: Option<f32>,
height: Option<f32>,
_: AvailableSpace,
_: AvailableSpace,
) -> Vec2 {
let mut size = self.size;
match (width, height) {
(None, None) => {}
(Some(width), None) => {
size.y = width * size.y / size.x;
size.x = width;
}
(None, Some(height)) => {
size.x = height * size.x / size.y;
size.y = height;
}
(Some(width), Some(height)) => {
size.x = width;
size.y = height;
}
}
size
}
}
/// Updates content size of the node based on the image provided
pub fn update_image_content_size_system(
textures: Res<Assets<Image>>,
#[cfg(feature = "bevy_text")] mut query: Query<
(&mut ContentSize, &UiImage, &mut UiImageSize),
(With<Node>, Without<Text>),
>,
#[cfg(not(feature = "bevy_text"))] mut query: Query<
(&mut ContentSize, &UiImage, &mut UiImageSize),
With<Node>,
>,
) {
for (mut content_size, image, mut image_size) in &mut query {
if let Some(texture) = textures.get(&image.texture) {
let size = Vec2::new(
texture.texture_descriptor.size.width as f32,
texture.texture_descriptor.size.height as f32,
);
// Update only if size has changed to avoid needless layout calculations
if size != image_size.size {
image_size.size = size;
content_size.set(ImageMeasure { size });
}
}
}
}
/// Updates content size of the node based on the texture atlas sprite
pub fn update_atlas_content_size_system(
atlases: Res<Assets<TextureAtlas>>,
#[cfg(feature = "bevy_text")] mut atlas_query: Query<
(
&mut ContentSize,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
&mut UiImageSize,
),
(With<Node>, Without<Text>, Without<UiImage>),
>,
#[cfg(not(feature = "bevy_text"))] mut atlas_query: Query<
(
&mut ContentSize,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
&mut UiImageSize,
),
(With<Node>, Without<UiImage>),
>,
) {
for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query {
if let Some(atlas) = atlases.get(atlas) {
let texture_rect = atlas.textures[atlas_image.index];
let size = Vec2::new(
texture_rect.max.x - texture_rect.min.x,
texture_rect.max.y - texture_rect.min.y,
);
// Update only if size has changed to avoid needless layout calculations
if size != image_size.size {
image_size.size = size;
content_size.set(ImageMeasure { size });
}
}
}
}