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>
This commit is contained in:
parent
9b006fdf75
commit
66b5128b6f
@ -329,25 +329,31 @@ pub fn extract_uinode_images(
|
||||
continue;
|
||||
}
|
||||
|
||||
let (rect, atlas_scaling) = match atlas {
|
||||
Some(atlas) => {
|
||||
let Some(layout) = texture_atlases.get(&atlas.layout) else {
|
||||
// Atlas not present in assets resource (should this warn the user?)
|
||||
continue;
|
||||
};
|
||||
let mut atlas_rect = layout.textures[atlas.index].as_rect();
|
||||
let atlas_scaling = uinode.size() / atlas_rect.size();
|
||||
atlas_rect.min *= atlas_scaling;
|
||||
atlas_rect.max *= atlas_scaling;
|
||||
(atlas_rect, Some(atlas_scaling))
|
||||
let atlas_rect = atlas
|
||||
.and_then(|s| s.texture_rect(&texture_atlases))
|
||||
.map(|r| r.as_rect());
|
||||
|
||||
let mut rect = match (atlas_rect, image.rect) {
|
||||
(None, None) => Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
(None, Some(image_rect)) => image_rect,
|
||||
(Some(atlas_rect), None) => atlas_rect,
|
||||
(Some(atlas_rect), Some(mut image_rect)) => {
|
||||
image_rect.min += atlas_rect.min;
|
||||
image_rect.max += atlas_rect.min;
|
||||
image_rect
|
||||
}
|
||||
None => (
|
||||
Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
None,
|
||||
),
|
||||
};
|
||||
|
||||
let atlas_scaling = if atlas_rect.is_some() || image.rect.is_some() {
|
||||
let atlas_scaling = uinode.size() / rect.size();
|
||||
rect.min *= atlas_scaling;
|
||||
rect.max *= atlas_scaling;
|
||||
Some(atlas_scaling)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ui_logical_viewport_size = camera_query
|
||||
|
||||
@ -275,11 +275,20 @@ pub fn extract_ui_texture_slices(
|
||||
continue;
|
||||
}
|
||||
|
||||
let atlas_rect = atlas.and_then(|atlas| {
|
||||
texture_atlases
|
||||
.get(&atlas.layout)
|
||||
.map(|layout| layout.textures[atlas.index].as_rect())
|
||||
});
|
||||
let atlas_rect = atlas
|
||||
.and_then(|s| s.texture_rect(&texture_atlases))
|
||||
.map(|r| r.as_rect());
|
||||
|
||||
let atlas_rect = match (atlas_rect, image.rect) {
|
||||
(None, None) => None,
|
||||
(None, Some(image_rect)) => Some(image_rect),
|
||||
(Some(atlas_rect), None) => Some(atlas_rect),
|
||||
(Some(atlas_rect), Some(mut image_rect)) => {
|
||||
image_rect.min += atlas_rect.min;
|
||||
image_rect.max += atlas_rect.min;
|
||||
Some(image_rect)
|
||||
}
|
||||
};
|
||||
|
||||
extracted_ui_slicers.slices.insert(
|
||||
commands.spawn_empty().id(),
|
||||
|
||||
@ -1837,6 +1837,12 @@ pub struct UiImage {
|
||||
pub flip_x: bool,
|
||||
/// Whether the image should be flipped along its y-axis
|
||||
pub flip_y: bool,
|
||||
/// An optional rectangle representing the region of the image to render, instead of rendering
|
||||
/// the full image. This is an easy one-off alternative to using a [`TextureAtlas`](bevy_sprite::TextureAtlas).
|
||||
///
|
||||
/// When used with a [`TextureAtlas`](bevy_sprite::TextureAtlas), the rect
|
||||
/// is offset by the atlas's minimal (top-left) corner position.
|
||||
pub rect: Option<Rect>,
|
||||
}
|
||||
|
||||
impl Default for UiImage {
|
||||
@ -1856,6 +1862,7 @@ impl Default for UiImage {
|
||||
texture: TRANSPARENT_IMAGE_HANDLE,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
rect: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1879,6 +1886,7 @@ impl UiImage {
|
||||
color,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
rect: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1902,6 +1910,12 @@ impl UiImage {
|
||||
self.flip_y = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn with_rect(mut self, rect: Rect) -> Self {
|
||||
self.rect = Some(rect);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Handle<Image>> for UiImage {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user