Skip to content

Commit 66b5128

Browse files
authored
Add rect field to UI image (#15095)
# Objective Fixes #14424 ## Solution Add a rect field to UiImage, and update the extraction of ui images and slices. ## Testing I tested all possible combinations of having a rect, using a texture atlas, setting image scale mode to sliced and image scale mode to tiled. See the showcase section. --- ## Showcase <img width="1279" alt="Screenshot 2024-09-08 at 16 23 05" src="https://github.com/user-attachments/assets/183e53eb-f27c-4c8e-9fd5-4678825db3b6"> <details> <summary>Click to view showcase</summary> ```rust use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) .add_systems(Startup, create_ui) .run(); } fn create_ui( mut commands: Commands, assets: Res<AssetServer>, mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>, ) { let texture = assets.load("textures/fantasy_ui_borders/numbered_slices.png"); let layout = TextureAtlasLayout::from_grid(UVec2::splat(16), 3, 3, None, None); let texture_atlas_layout = texture_atlas_layouts.add(layout); commands.spawn(Camera2dBundle::default()); let style = Style { width: Val::Px(96.), height: Val::Px(96.), ..default() }; commands .spawn(NodeBundle { ..default() }) .with_children(|parent| { // nothing parent.spawn(ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }); // with rect parent.spawn(ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)), style: style.clone(), ..default() }); // with rect and texture atlas parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 1, }, )); // with texture atlas parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 2, }, )); // with texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(16.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with rect and texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)), style: style.clone(), ..default() }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(2.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with rect, texture atlas and texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 1, }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(1.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with texture atlas and texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 2, }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(2.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); // with rect and tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)), style: style.clone(), ..default() }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); // with rect, texture atlas and tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 1, }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); // with texture atlas and tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 2, }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); }); } ``` </details>
1 parent 9b006fd commit 66b5128

File tree

3 files changed

+52
-23
lines changed

3 files changed

+52
-23
lines changed

crates/bevy_ui/src/render/mod.rs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -329,25 +329,31 @@ pub fn extract_uinode_images(
329329
continue;
330330
}
331331

332-
let (rect, atlas_scaling) = match atlas {
333-
Some(atlas) => {
334-
let Some(layout) = texture_atlases.get(&atlas.layout) else {
335-
// Atlas not present in assets resource (should this warn the user?)
336-
continue;
337-
};
338-
let mut atlas_rect = layout.textures[atlas.index].as_rect();
339-
let atlas_scaling = uinode.size() / atlas_rect.size();
340-
atlas_rect.min *= atlas_scaling;
341-
atlas_rect.max *= atlas_scaling;
342-
(atlas_rect, Some(atlas_scaling))
332+
let atlas_rect = atlas
333+
.and_then(|s| s.texture_rect(&texture_atlases))
334+
.map(|r| r.as_rect());
335+
336+
let mut rect = match (atlas_rect, image.rect) {
337+
(None, None) => Rect {
338+
min: Vec2::ZERO,
339+
max: uinode.calculated_size,
340+
},
341+
(None, Some(image_rect)) => image_rect,
342+
(Some(atlas_rect), None) => atlas_rect,
343+
(Some(atlas_rect), Some(mut image_rect)) => {
344+
image_rect.min += atlas_rect.min;
345+
image_rect.max += atlas_rect.min;
346+
image_rect
343347
}
344-
None => (
345-
Rect {
346-
min: Vec2::ZERO,
347-
max: uinode.calculated_size,
348-
},
349-
None,
350-
),
348+
};
349+
350+
let atlas_scaling = if atlas_rect.is_some() || image.rect.is_some() {
351+
let atlas_scaling = uinode.size() / rect.size();
352+
rect.min *= atlas_scaling;
353+
rect.max *= atlas_scaling;
354+
Some(atlas_scaling)
355+
} else {
356+
None
351357
};
352358

353359
let ui_logical_viewport_size = camera_query

crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,20 @@ pub fn extract_ui_texture_slices(
275275
continue;
276276
}
277277

278-
let atlas_rect = atlas.and_then(|atlas| {
279-
texture_atlases
280-
.get(&atlas.layout)
281-
.map(|layout| layout.textures[atlas.index].as_rect())
282-
});
278+
let atlas_rect = atlas
279+
.and_then(|s| s.texture_rect(&texture_atlases))
280+
.map(|r| r.as_rect());
281+
282+
let atlas_rect = match (atlas_rect, image.rect) {
283+
(None, None) => None,
284+
(None, Some(image_rect)) => Some(image_rect),
285+
(Some(atlas_rect), None) => Some(atlas_rect),
286+
(Some(atlas_rect), Some(mut image_rect)) => {
287+
image_rect.min += atlas_rect.min;
288+
image_rect.max += atlas_rect.min;
289+
Some(image_rect)
290+
}
291+
};
283292

284293
extracted_ui_slicers.slices.insert(
285294
commands.spawn_empty().id(),

crates/bevy_ui/src/ui_node.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,12 @@ pub struct UiImage {
18371837
pub flip_x: bool,
18381838
/// Whether the image should be flipped along its y-axis
18391839
pub flip_y: bool,
1840+
/// An optional rectangle representing the region of the image to render, instead of rendering
1841+
/// the full image. This is an easy one-off alternative to using a [`TextureAtlas`](bevy_sprite::TextureAtlas).
1842+
///
1843+
/// When used with a [`TextureAtlas`](bevy_sprite::TextureAtlas), the rect
1844+
/// is offset by the atlas's minimal (top-left) corner position.
1845+
pub rect: Option<Rect>,
18401846
}
18411847

18421848
impl Default for UiImage {
@@ -1856,6 +1862,7 @@ impl Default for UiImage {
18561862
texture: TRANSPARENT_IMAGE_HANDLE,
18571863
flip_x: false,
18581864
flip_y: false,
1865+
rect: None,
18591866
}
18601867
}
18611868
}
@@ -1879,6 +1886,7 @@ impl UiImage {
18791886
color,
18801887
flip_x: false,
18811888
flip_y: false,
1889+
rect: None,
18821890
}
18831891
}
18841892

@@ -1902,6 +1910,12 @@ impl UiImage {
19021910
self.flip_y = true;
19031911
self
19041912
}
1913+
1914+
#[must_use]
1915+
pub const fn with_rect(mut self, rect: Rect) -> Self {
1916+
self.rect = Some(rect);
1917+
self
1918+
}
19051919
}
19061920

19071921
impl From<Handle<Image>> for UiImage {

0 commit comments

Comments
 (0)