Skip to content

Commit 0ebcf74

Browse files
cartmockersf
authored andcommitted
Move TextureAtlas into UiImage and remove impl Component for TextureAtlas (#16072)
# Objective Fixes #16064 ## Solution - Add TextureAtlas to `UiImage::texture_atlas` - Add `TextureAtlas::from_atlas_image` for parity with `Sprite` - Rename `UiImage::texture` to `UiImage::image` for parity with `Sprite` - Port relevant implementations and uses - Remove `derive(Component)` for `TextureAtlas` --- ## Migration Guide Before: ```rust commands.spawn(( UiImage::new(image), TextureAtlas { index, layout }, )); ``` After: ```rust commands.spawn(UiImage::from_atlas_image(image, TextureAtlas { index, layout })); ``` Before: ```rust commands.spawn(UiImage { texture: some_image, ..default() }) ``` After: ```rust commands.spawn(UiImage { image: some_image, ..default() }) ```
1 parent 22bfe7e commit 0ebcf74

10 files changed

+71
-70
lines changed

crates/bevy_sprite/src/texture_atlas.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use bevy_asset::{Asset, AssetId, Assets, Handle};
2-
use bevy_ecs::{component::Component, reflect::ReflectComponent};
32
use bevy_math::{URect, UVec2};
43
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
54
#[cfg(feature = "serialize")]
@@ -152,7 +151,7 @@ impl TextureAtlasLayout {
152151
}
153152
}
154153

155-
/// Component used to draw a specific section of a texture.
154+
/// An index into a [`TextureAtlasLayout`], which corresponds to a specific section of a texture.
156155
///
157156
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
158157
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
@@ -164,8 +163,8 @@ impl TextureAtlasLayout {
164163
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
165164
/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
166165
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
167-
#[derive(Component, Default, Debug, Clone, Reflect)]
168-
#[reflect(Component, Default, Debug)]
166+
#[derive(Default, Debug, Clone, Reflect)]
167+
#[reflect(Default, Debug)]
169168
pub struct TextureAtlas {
170169
/// Texture atlas layout handle
171170
pub layout: Handle<TextureAtlasLayout>,

crates/bevy_ui/src/render/mod.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use bevy_render::{
4040
ExtractSchedule, Render,
4141
};
4242
use bevy_sprite::TextureAtlasLayout;
43-
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas};
43+
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents};
4444

4545
use crate::{Display, Node};
4646
use bevy_text::{ComputedTextBlock, PositionedGlyph, TextColor, TextLayoutInfo};
@@ -317,14 +317,13 @@ pub fn extract_uinode_images(
317317
Option<&CalculatedClip>,
318318
Option<&TargetCamera>,
319319
&UiImage,
320-
Option<&TextureAtlas>,
321320
),
322321
Without<ImageScaleMode>,
323322
>,
324323
>,
325324
mapping: Extract<Query<RenderEntity>>,
326325
) {
327-
for (entity, uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query {
326+
for (entity, uinode, transform, view_visibility, clip, camera, image) in &uinode_query {
328327
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
329328
else {
330329
continue;
@@ -337,12 +336,14 @@ pub fn extract_uinode_images(
337336
// Skip invisible images
338337
if !view_visibility.get()
339338
|| image.color.is_fully_transparent()
340-
|| image.texture.id() == TRANSPARENT_IMAGE_HANDLE.id()
339+
|| image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
341340
{
342341
continue;
343342
}
344343

345-
let atlas_rect = atlas
344+
let atlas_rect = image
345+
.texture_atlas
346+
.as_ref()
346347
.and_then(|s| s.texture_rect(&texture_atlases))
347348
.map(|r| r.as_rect());
348349

@@ -376,7 +377,7 @@ pub fn extract_uinode_images(
376377
color: image.color.into(),
377378
rect,
378379
clip: clip.map(|clip| clip.clip),
379-
image: image.texture.id(),
380+
image: image.image.id(),
380381
camera_entity: render_camera_entity,
381382
item: ExtractedUiItem::Node {
382383
atlas_scaling,

crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs

+8-17
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ use bevy_render::{
2424
Extract, ExtractSchedule, Render, RenderSet,
2525
};
2626
use bevy_sprite::{
27-
ImageScaleMode, SliceScaleMode, SpriteAssetEvents, TextureAtlas, TextureAtlasLayout,
28-
TextureSlicer,
27+
ImageScaleMode, SliceScaleMode, SpriteAssetEvents, TextureAtlasLayout, TextureSlicer,
2928
};
3029
use bevy_transform::prelude::GlobalTransform;
3130
use bevy_utils::HashMap;
@@ -258,22 +257,12 @@ pub fn extract_ui_texture_slices(
258257
Option<&TargetCamera>,
259258
&UiImage,
260259
&ImageScaleMode,
261-
Option<&TextureAtlas>,
262260
)>,
263261
>,
264262
mapping: Extract<Query<RenderEntity>>,
265263
) {
266-
for (
267-
entity,
268-
uinode,
269-
transform,
270-
view_visibility,
271-
clip,
272-
camera,
273-
image,
274-
image_scale_mode,
275-
atlas,
276-
) in &slicers_query
264+
for (entity, uinode, transform, view_visibility, clip, camera, image, image_scale_mode) in
265+
&slicers_query
277266
{
278267
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
279268
else {
@@ -287,12 +276,14 @@ pub fn extract_ui_texture_slices(
287276
// Skip invisible images
288277
if !view_visibility.get()
289278
|| image.color.is_fully_transparent()
290-
|| image.texture.id() == TRANSPARENT_IMAGE_HANDLE.id()
279+
|| image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
291280
{
292281
continue;
293282
}
294283

295-
let atlas_rect = atlas
284+
let atlas_rect = image
285+
.texture_atlas
286+
.as_ref()
296287
.and_then(|s| s.texture_rect(&texture_atlases))
297288
.map(|r| r.as_rect());
298289

@@ -318,7 +309,7 @@ pub fn extract_ui_texture_slices(
318309
max: uinode.calculated_size,
319310
},
320311
clip: clip.map(|clip| clip.clip),
321-
image: image.texture.id(),
312+
image: image.image.id(),
322313
camera_entity,
323314
image_scale_mode: image_scale_mode.clone(),
324315
atlas_rect,

crates/bevy_ui/src/ui_node.rs

+20-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use bevy_render::{
99
texture::{Image, TRANSPARENT_IMAGE_HANDLE},
1010
view::Visibility,
1111
};
12-
use bevy_sprite::BorderRect;
12+
use bevy_sprite::{BorderRect, TextureAtlas};
1313
use bevy_transform::components::Transform;
1414
use bevy_utils::warn_once;
1515
use bevy_window::{PrimaryWindow, WindowRef};
@@ -2053,15 +2053,17 @@ pub struct UiImage {
20532053
/// Handle to the texture.
20542054
///
20552055
/// This defaults to a [`TRANSPARENT_IMAGE_HANDLE`], which points to a fully transparent 1x1 texture.
2056-
pub texture: Handle<Image>,
2056+
pub image: Handle<Image>,
2057+
/// The (optional) texture atlas used to render the image
2058+
pub texture_atlas: Option<TextureAtlas>,
20572059
/// Whether the image should be flipped along its x-axis
20582060
pub flip_x: bool,
20592061
/// Whether the image should be flipped along its y-axis
20602062
pub flip_y: bool,
20612063
/// An optional rectangle representing the region of the image to render, instead of rendering
2062-
/// the full image. This is an easy one-off alternative to using a [`TextureAtlas`](bevy_sprite::TextureAtlas).
2064+
/// the full image. This is an easy one-off alternative to using a [`TextureAtlas`].
20632065
///
2064-
/// When used with a [`TextureAtlas`](bevy_sprite::TextureAtlas), the rect
2066+
/// When used with a [`TextureAtlas`], the rect
20652067
/// is offset by the atlas's minimal (top-left) corner position.
20662068
pub rect: Option<Rect>,
20672069
}
@@ -2079,8 +2081,9 @@ impl Default for UiImage {
20792081
// This should be white because the tint is multiplied with the image,
20802082
// so if you set an actual image with default tint you'd want its original colors
20812083
color: Color::WHITE,
2084+
texture_atlas: None,
20822085
// This texture needs to be transparent by default, to avoid covering the background color
2083-
texture: TRANSPARENT_IMAGE_HANDLE,
2086+
image: TRANSPARENT_IMAGE_HANDLE,
20842087
flip_x: false,
20852088
flip_y: false,
20862089
rect: None,
@@ -2092,7 +2095,7 @@ impl UiImage {
20922095
/// Create a new [`UiImage`] with the given texture.
20932096
pub fn new(texture: Handle<Image>) -> Self {
20942097
Self {
2095-
texture,
2098+
image: texture,
20962099
color: Color::WHITE,
20972100
..Default::default()
20982101
}
@@ -2103,14 +2106,24 @@ impl UiImage {
21032106
/// This is primarily useful for debugging / mocking the extents of your image.
21042107
pub fn solid_color(color: Color) -> Self {
21052108
Self {
2106-
texture: Handle::default(),
2109+
image: Handle::default(),
21072110
color,
21082111
flip_x: false,
21092112
flip_y: false,
2113+
texture_atlas: None,
21102114
rect: None,
21112115
}
21122116
}
21132117

2118+
/// Create a [`UiImage`] from an image, with an associated texture atlas
2119+
pub fn from_atlas_image(image: Handle<Image>, atlas: TextureAtlas) -> Self {
2120+
Self {
2121+
image,
2122+
texture_atlas: Some(atlas),
2123+
..Default::default()
2124+
}
2125+
}
2126+
21142127
/// Set the color tint
21152128
#[must_use]
21162129
pub const fn with_color(mut self, color: Color) -> Self {

crates/bevy_ui/src/widget/image.rs

+5-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bevy_ecs::prelude::*;
44
use bevy_math::{UVec2, Vec2};
55
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
66
use bevy_render::texture::Image;
7-
use bevy_sprite::{TextureAtlas, TextureAtlasLayout};
7+
use bevy_sprite::TextureAtlasLayout;
88
use bevy_window::{PrimaryWindow, Window};
99
use taffy::{MaybeMath, MaybeResolve};
1010

@@ -97,26 +97,18 @@ pub fn update_image_content_size_system(
9797
textures: Res<Assets<Image>>,
9898

9999
atlases: Res<Assets<TextureAtlasLayout>>,
100-
mut query: Query<
101-
(
102-
&mut ContentSize,
103-
&UiImage,
104-
&mut UiImageSize,
105-
Option<&TextureAtlas>,
106-
),
107-
UpdateImageFilter,
108-
>,
100+
mut query: Query<(&mut ContentSize, &UiImage, &mut UiImageSize), UpdateImageFilter>,
109101
) {
110102
let combined_scale_factor = windows
111103
.get_single()
112104
.map(|window| window.resolution.scale_factor())
113105
.unwrap_or(1.)
114106
* ui_scale.0;
115107

116-
for (mut content_size, image, mut image_size, atlas_image) in &mut query {
117-
if let Some(size) = match atlas_image {
108+
for (mut content_size, image, mut image_size) in &mut query {
109+
if let Some(size) = match &image.texture_atlas {
118110
Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()),
119-
None => textures.get(&image.texture).map(Image::size),
111+
None => textures.get(&image.image).map(Image::size),
120112
} {
121113
// Update only if size or scale factor has changed to avoid needless layout calculations
122114
if size != image_size.size

examples/3d/auto_exposure.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ fn setup(
116116

117117
commands.spawn((
118118
UiImage {
119-
texture: metering_mask,
119+
image: metering_mask,
120120
..default()
121121
},
122122
Node {

examples/stress_tests/many_animated_sprites.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ fn setup(
8484
commands.spawn((
8585
Sprite {
8686
image: texture_handle.clone(),
87+
texture_atlas: Some(TextureAtlas::from(texture_atlas_handle.clone())),
8788
custom_size: Some(tile_size),
8889
..default()
8990
},
@@ -92,7 +93,6 @@ fn setup(
9293
rotation,
9394
scale,
9495
},
95-
TextureAtlas::from(texture_atlas_handle.clone()),
9696
AnimationTimer(timer),
9797
));
9898
}
@@ -112,13 +112,16 @@ struct AnimationTimer(Timer);
112112
fn animate_sprite(
113113
time: Res<Time>,
114114
texture_atlases: Res<Assets<TextureAtlasLayout>>,
115-
mut query: Query<(&mut AnimationTimer, &mut TextureAtlas)>,
115+
mut query: Query<(&mut AnimationTimer, &mut Sprite)>,
116116
) {
117-
for (mut timer, mut sheet) in query.iter_mut() {
117+
for (mut timer, mut sprite) in query.iter_mut() {
118118
timer.tick(time.delta());
119119
if timer.just_finished() {
120-
let texture_atlas = texture_atlases.get(&sheet.layout).unwrap();
121-
sheet.index = (sheet.index + 1) % texture_atlas.textures.len();
120+
let Some(atlas) = &mut sprite.texture_atlas else {
121+
continue;
122+
};
123+
let texture_atlas = texture_atlases.get(&atlas.layout).unwrap();
124+
atlas.index = (atlas.index + 1) % texture_atlas.textures.len();
122125
}
123126
}
124127
}

examples/ui/ui_texture_atlas.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,13 @@ fn setup(
4444
})
4545
.with_children(|parent| {
4646
parent.spawn((
47-
UiImage::new(texture_handle),
47+
UiImage::from_atlas_image(texture_handle, TextureAtlas::from(texture_atlas_handle)),
4848
Node {
4949
width: Val::Px(256.),
5050
height: Val::Px(256.),
5151
..default()
5252
},
5353
BackgroundColor(ANTIQUE_WHITE.into()),
54-
TextureAtlas::from(texture_atlas_handle),
5554
Outline::new(Val::Px(8.0), Val::ZERO, CRIMSON.into()),
5655
));
5756
parent
@@ -65,13 +64,12 @@ fn setup(
6564
});
6665
}
6766

68-
fn increment_atlas_index(
69-
mut atlas_images: Query<&mut TextureAtlas>,
70-
keyboard: Res<ButtonInput<KeyCode>>,
71-
) {
67+
fn increment_atlas_index(mut ui_images: Query<&mut UiImage>, keyboard: Res<ButtonInput<KeyCode>>) {
7268
if keyboard.just_pressed(KeyCode::Space) {
73-
for mut atlas_image in &mut atlas_images {
74-
atlas_image.index = (atlas_image.index + 1) % 6;
69+
for mut ui_image in &mut ui_images {
70+
if let Some(atlas) = &mut ui_image.texture_atlas {
71+
atlas.index = (atlas.index + 1) % 6;
72+
}
7573
}
7674
}
7775
}

examples/ui/ui_texture_atlas_slice.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,19 @@ fn main() {
1919

2020
fn button_system(
2121
mut interaction_query: Query<
22-
(&Interaction, &mut TextureAtlas, &Children, &mut UiImage),
22+
(&Interaction, &Children, &mut UiImage),
2323
(Changed<Interaction>, With<Button>),
2424
>,
2525
mut text_query: Query<&mut Text>,
2626
) {
27-
for (interaction, mut atlas, children, mut image) in &mut interaction_query {
27+
for (interaction, children, mut image) in &mut interaction_query {
2828
let mut text = text_query.get_mut(children[0]).unwrap();
2929
match *interaction {
3030
Interaction::Pressed => {
3131
**text = "Press".to_string();
32-
atlas.index = (atlas.index + 1) % 30;
32+
if let Some(atlas) = &mut image.texture_atlas {
33+
atlas.index = (atlas.index + 1) % 30;
34+
}
3335
image.color = GOLD.into();
3436
}
3537
Interaction::Hovered => {
@@ -79,7 +81,13 @@ fn setup(
7981
parent
8082
.spawn((
8183
Button,
82-
UiImage::new(texture_handle.clone()),
84+
UiImage::from_atlas_image(
85+
texture_handle.clone(),
86+
TextureAtlas {
87+
index: idx,
88+
layout: atlas_layout_handle.clone(),
89+
},
90+
),
8391
Node {
8492
width: Val::Px(w),
8593
height: Val::Px(h),
@@ -91,10 +99,6 @@ fn setup(
9199
..default()
92100
},
93101
ImageScaleMode::Sliced(slicer.clone()),
94-
TextureAtlas {
95-
index: idx,
96-
layout: atlas_layout_handle.clone(),
97-
},
98102
))
99103
.with_children(|parent| {
100104
parent.spawn((

examples/ui/ui_texture_slice_flip_and_tile.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
5858
] {
5959
parent.spawn((
6060
UiImage {
61-
texture: image.clone(),
61+
image: image.clone(),
6262
flip_x,
6363
flip_y,
6464
..default()

0 commit comments

Comments
 (0)