mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Slicing support for texture atlas (#12059)
# Objective Follow up to #11600 and #10588 https://github.com/bevyengine/bevy/issues/11944 made clear that some people want to use slicing with texture atlases ## Changelog * Added support for `TextureAtlas` slicing and tiling. `SpriteSheetBundle` and `AtlasImageBundle` can now use `ImageScaleMode` * Added new `ui_texture_atlas_slice` example using a texture sheet <img width="798" alt="Screenshot 2024-02-23 at 11 58 35" src="https://github.com/bevyengine/bevy/assets/26703856/47a8b764-127c-4a06-893f-181703777501"> --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com>
This commit is contained in:
parent
dc40cd134f
commit
fc202f2e3d
10 changed files with 283 additions and 47 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -2510,6 +2510,17 @@ description = "Illustrates how to use 9 Slicing in UI"
|
||||||
category = "UI (User Interface)"
|
category = "UI (User Interface)"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "ui_texture_atlas_slice"
|
||||||
|
path = "examples/ui/ui_texture_atlas_slice.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[package.metadata.example.ui_texture_atlas_slice]
|
||||||
|
name = "UI Texture Atlas Slice"
|
||||||
|
description = "Illustrates how to use 9 Slicing for TextureAtlases in UI"
|
||||||
|
category = "UI (User Interface)"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "viewport_debug"
|
name = "viewport_debug"
|
||||||
path = "examples/ui/viewport_debug.rs"
|
path = "examples/ui/viewport_debug.rs"
|
||||||
|
|
BIN
assets/textures/fantasy_ui_borders/border_sheet.png
Normal file
BIN
assets/textures/fantasy_ui_borders/border_sheet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -32,8 +32,6 @@ pub struct Sprite {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls how the image is altered when scaled.
|
/// Controls how the image is altered when scaled.
|
||||||
///
|
|
||||||
/// Note: This is not yet compatible with texture atlases
|
|
||||||
#[derive(Component, Debug, Clone, Reflect)]
|
#[derive(Component, Debug, Clone, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub enum ImageScaleMode {
|
pub enum ImageScaleMode {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{ExtractedSprite, ImageScaleMode, Sprite};
|
use crate::{ExtractedSprite, ImageScaleMode, Sprite, TextureAtlas, TextureAtlasLayout};
|
||||||
|
|
||||||
use super::TextureSlice;
|
use super::TextureSlice;
|
||||||
use bevy_asset::{AssetEvent, Assets, Handle};
|
use bevy_asset::{AssetEvent, Assets, Handle};
|
||||||
|
@ -63,37 +63,55 @@ impl ComputedTextureSlices {
|
||||||
/// will be computed according to the `image_handle` dimensions or the sprite rect.
|
/// will be computed according to the `image_handle` dimensions or the sprite rect.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the image asset is not loaded
|
/// Returns `None` if the image asset is not loaded
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `sprite` - The sprite component, will be used to find the draw area size
|
||||||
|
/// * `scale_mode` - The image scaling component
|
||||||
|
/// * `image_handle` - The texture to slice or tile
|
||||||
|
/// * `images` - The image assets, use to retrieve the image dimensions
|
||||||
|
/// * `atlas` - Optional texture atlas, if set the slicing will happen on the matching sub section
|
||||||
|
/// of the texture
|
||||||
|
/// * `atlas_layouts` - The atlas layout assets, used to retrieve the texture atlas section rect
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn compute_sprite_slices(
|
fn compute_sprite_slices(
|
||||||
sprite: &Sprite,
|
sprite: &Sprite,
|
||||||
scale_mode: &ImageScaleMode,
|
scale_mode: &ImageScaleMode,
|
||||||
image_handle: &Handle<Image>,
|
image_handle: &Handle<Image>,
|
||||||
images: &Assets<Image>,
|
images: &Assets<Image>,
|
||||||
|
atlas: Option<&TextureAtlas>,
|
||||||
|
atlas_layouts: &Assets<TextureAtlasLayout>,
|
||||||
) -> Option<ComputedTextureSlices> {
|
) -> Option<ComputedTextureSlices> {
|
||||||
let image_size = images.get(image_handle).map(|i| {
|
let (image_size, texture_rect) = match atlas {
|
||||||
Vec2::new(
|
Some(a) => {
|
||||||
i.texture_descriptor.size.width as f32,
|
let layout = atlas_layouts.get(&a.layout)?;
|
||||||
i.texture_descriptor.size.height as f32,
|
(
|
||||||
)
|
layout.size.as_vec2(),
|
||||||
})?;
|
layout.textures.get(a.index)?.as_rect(),
|
||||||
let slices = match scale_mode {
|
)
|
||||||
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(
|
}
|
||||||
sprite.rect.unwrap_or(Rect {
|
None => {
|
||||||
|
let image = images.get(image_handle)?;
|
||||||
|
let size = Vec2::new(
|
||||||
|
image.texture_descriptor.size.width as f32,
|
||||||
|
image.texture_descriptor.size.height as f32,
|
||||||
|
);
|
||||||
|
let rect = sprite.rect.unwrap_or(Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
max: image_size,
|
max: size,
|
||||||
}),
|
});
|
||||||
sprite.custom_size,
|
(size, rect)
|
||||||
),
|
}
|
||||||
|
};
|
||||||
|
let slices = match scale_mode {
|
||||||
|
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, sprite.custom_size),
|
||||||
ImageScaleMode::Tiled {
|
ImageScaleMode::Tiled {
|
||||||
tile_x,
|
tile_x,
|
||||||
tile_y,
|
tile_y,
|
||||||
stretch_value,
|
stretch_value,
|
||||||
} => {
|
} => {
|
||||||
let slice = TextureSlice {
|
let slice = TextureSlice {
|
||||||
texture_rect: sprite.rect.unwrap_or(Rect {
|
texture_rect,
|
||||||
min: Vec2::ZERO,
|
|
||||||
max: image_size,
|
|
||||||
}),
|
|
||||||
draw_size: sprite.custom_size.unwrap_or(image_size),
|
draw_size: sprite.custom_size.unwrap_or(image_size),
|
||||||
offset: Vec2::ZERO,
|
offset: Vec2::ZERO,
|
||||||
};
|
};
|
||||||
|
@ -109,7 +127,14 @@ pub(crate) fn compute_slices_on_asset_event(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut events: EventReader<AssetEvent<Image>>,
|
mut events: EventReader<AssetEvent<Image>>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
sprites: Query<(Entity, &ImageScaleMode, &Sprite, &Handle<Image>)>,
|
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
|
||||||
|
sprites: Query<(
|
||||||
|
Entity,
|
||||||
|
&ImageScaleMode,
|
||||||
|
&Sprite,
|
||||||
|
&Handle<Image>,
|
||||||
|
Option<&TextureAtlas>,
|
||||||
|
)>,
|
||||||
) {
|
) {
|
||||||
// We store the asset ids of added/modified image assets
|
// We store the asset ids of added/modified image assets
|
||||||
let added_handles: HashSet<_> = events
|
let added_handles: HashSet<_> = events
|
||||||
|
@ -123,11 +148,18 @@ pub(crate) fn compute_slices_on_asset_event(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We recompute the sprite slices for sprite entities with a matching asset handle id
|
// We recompute the sprite slices for sprite entities with a matching asset handle id
|
||||||
for (entity, scale_mode, sprite, image_handle) in &sprites {
|
for (entity, scale_mode, sprite, image_handle, atlas) in &sprites {
|
||||||
if !added_handles.contains(&image_handle.id()) {
|
if !added_handles.contains(&image_handle.id()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(slices) = compute_sprite_slices(sprite, scale_mode, image_handle, &images) {
|
if let Some(slices) = compute_sprite_slices(
|
||||||
|
sprite,
|
||||||
|
scale_mode,
|
||||||
|
image_handle,
|
||||||
|
&images,
|
||||||
|
atlas,
|
||||||
|
&atlas_layouts,
|
||||||
|
) {
|
||||||
commands.entity(entity).insert(slices);
|
commands.entity(entity).insert(slices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,17 +170,32 @@ pub(crate) fn compute_slices_on_asset_event(
|
||||||
pub(crate) fn compute_slices_on_sprite_change(
|
pub(crate) fn compute_slices_on_sprite_change(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
|
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
|
||||||
changed_sprites: Query<
|
changed_sprites: Query<
|
||||||
(Entity, &ImageScaleMode, &Sprite, &Handle<Image>),
|
(
|
||||||
|
Entity,
|
||||||
|
&ImageScaleMode,
|
||||||
|
&Sprite,
|
||||||
|
&Handle<Image>,
|
||||||
|
Option<&TextureAtlas>,
|
||||||
|
),
|
||||||
Or<(
|
Or<(
|
||||||
Changed<ImageScaleMode>,
|
Changed<ImageScaleMode>,
|
||||||
Changed<Handle<Image>>,
|
Changed<Handle<Image>>,
|
||||||
Changed<Sprite>,
|
Changed<Sprite>,
|
||||||
|
Changed<TextureAtlas>,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
for (entity, scale_mode, sprite, image_handle) in &changed_sprites {
|
for (entity, scale_mode, sprite, image_handle, atlas) in &changed_sprites {
|
||||||
if let Some(slices) = compute_sprite_slices(sprite, scale_mode, image_handle, &images) {
|
if let Some(slices) = compute_sprite_slices(
|
||||||
|
sprite,
|
||||||
|
scale_mode,
|
||||||
|
image_handle,
|
||||||
|
&images,
|
||||||
|
atlas,
|
||||||
|
&atlas_layouts,
|
||||||
|
) {
|
||||||
commands.entity(entity).insert(slices);
|
commands.entity(entity).insert(slices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl TextureSlicer {
|
||||||
TextureSlice {
|
TextureSlice {
|
||||||
texture_rect: Rect {
|
texture_rect: Rect {
|
||||||
min: vec2(base_rect.max.x - right, base_rect.min.y),
|
min: vec2(base_rect.max.x - right, base_rect.min.y),
|
||||||
max: vec2(base_rect.max.x, top),
|
max: vec2(base_rect.max.x, base_rect.min.y + top),
|
||||||
},
|
},
|
||||||
draw_size: vec2(right, top) * min_coef,
|
draw_size: vec2(right, top) * min_coef,
|
||||||
offset: vec2(
|
offset: vec2(
|
||||||
|
@ -198,6 +198,9 @@ impl TextureSlicer {
|
||||||
///
|
///
|
||||||
/// * `rect` - The section of the texture to slice in 9 parts
|
/// * `rect` - The section of the texture to slice in 9 parts
|
||||||
/// * `render_size` - The optional draw size of the texture. If not set the `rect` size will be used.
|
/// * `render_size` - The optional draw size of the texture. If not set the `rect` size will be used.
|
||||||
|
//
|
||||||
|
// TODO: Support `URect` and `UVec2` instead (See `https://github.com/bevyengine/bevy/pull/11698`)
|
||||||
|
//
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn compute_slices(&self, rect: Rect, render_size: Option<Vec2>) -> Vec<TextureSlice> {
|
pub fn compute_slices(&self, rect: Rect, render_size: Option<Vec2>) -> Vec<TextureSlice> {
|
||||||
let render_size = render_size.unwrap_or_else(|| rect.size());
|
let render_size = render_size.unwrap_or_else(|| rect.size());
|
||||||
|
|
|
@ -122,6 +122,11 @@ pub struct ImageBundle {
|
||||||
|
|
||||||
/// A UI node that is a texture atlas sprite
|
/// A UI node that is a texture atlas sprite
|
||||||
///
|
///
|
||||||
|
/// # Extra behaviours
|
||||||
|
///
|
||||||
|
/// You may add the following components to enable additional behaviours
|
||||||
|
/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture
|
||||||
|
///
|
||||||
/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component.
|
/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component.
|
||||||
#[deprecated(
|
#[deprecated(
|
||||||
since = "0.14.0",
|
since = "0.14.0",
|
||||||
|
@ -295,7 +300,7 @@ where
|
||||||
///
|
///
|
||||||
/// You may add the following components to enable additional behaviours:
|
/// You may add the following components to enable additional behaviours:
|
||||||
/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture
|
/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture
|
||||||
/// - [`TextureAtlas`] to draw specific sections of the texture
|
/// - [`TextureAtlas`] to draw specific section of the texture
|
||||||
///
|
///
|
||||||
/// Note that `ImageScaleMode` is currently not compatible with `TextureAtlas`.
|
/// Note that `ImageScaleMode` is currently not compatible with `TextureAtlas`.
|
||||||
#[derive(Bundle, Clone, Debug)]
|
#[derive(Bundle, Clone, Debug)]
|
||||||
|
|
|
@ -6,7 +6,7 @@ use bevy_asset::{AssetEvent, Assets};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_math::{Rect, Vec2};
|
use bevy_math::{Rect, Vec2};
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
use bevy_sprite::{ImageScaleMode, TextureSlice};
|
use bevy_sprite::{ImageScaleMode, TextureAtlas, TextureAtlasLayout, TextureSlice};
|
||||||
use bevy_transform::prelude::*;
|
use bevy_transform::prelude::*;
|
||||||
use bevy_utils::HashSet;
|
use bevy_utils::HashSet;
|
||||||
|
|
||||||
|
@ -74,25 +74,48 @@ impl ComputedTextureSlices {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates sprite slices for a `sprite` given a `scale_mode`. The slices
|
/// Generates sprite slices for a `sprite` given a `scale_mode`. The slices
|
||||||
/// will be computed according to the `image_handle` dimensions or the sprite rect.
|
/// will be computed according to the `image_handle` dimensions.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the image asset is not loaded
|
/// Returns `None` if the image asset is not loaded
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `draw_area` - The size of the drawing area the slices will have to fit into
|
||||||
|
/// * `scale_mode` - The image scaling component
|
||||||
|
/// * `image_handle` - The texture to slice or tile
|
||||||
|
/// * `images` - The image assets, use to retrieve the image dimensions
|
||||||
|
/// * `atlas` - Optional texture atlas, if set the slicing will happen on the matching sub section
|
||||||
|
/// of the texture
|
||||||
|
/// * `atlas_layouts` - The atlas layout assets, used to retrieve the texture atlas section rect
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn compute_texture_slices(
|
fn compute_texture_slices(
|
||||||
draw_area: Vec2,
|
draw_area: Vec2,
|
||||||
scale_mode: &ImageScaleMode,
|
scale_mode: &ImageScaleMode,
|
||||||
image_handle: &UiImage,
|
image_handle: &UiImage,
|
||||||
images: &Assets<Image>,
|
images: &Assets<Image>,
|
||||||
|
atlas: Option<&TextureAtlas>,
|
||||||
|
atlas_layouts: &Assets<TextureAtlasLayout>,
|
||||||
) -> Option<ComputedTextureSlices> {
|
) -> Option<ComputedTextureSlices> {
|
||||||
let image_size = images.get(&image_handle.texture).map(|i| {
|
let (image_size, texture_rect) = match atlas {
|
||||||
Vec2::new(
|
Some(a) => {
|
||||||
i.texture_descriptor.size.width as f32,
|
let layout = atlas_layouts.get(&a.layout)?;
|
||||||
i.texture_descriptor.size.height as f32,
|
(
|
||||||
)
|
layout.size.as_vec2(),
|
||||||
})?;
|
layout.textures.get(a.index)?.as_rect(),
|
||||||
let texture_rect = Rect {
|
)
|
||||||
min: Vec2::ZERO,
|
}
|
||||||
max: image_size,
|
None => {
|
||||||
|
let image = images.get(&image_handle.texture)?;
|
||||||
|
let size = Vec2::new(
|
||||||
|
image.texture_descriptor.size.width as f32,
|
||||||
|
image.texture_descriptor.size.height as f32,
|
||||||
|
);
|
||||||
|
let rect = Rect {
|
||||||
|
min: Vec2::ZERO,
|
||||||
|
max: size,
|
||||||
|
};
|
||||||
|
(size, rect)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let slices = match scale_mode {
|
let slices = match scale_mode {
|
||||||
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, Some(draw_area)),
|
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, Some(draw_area)),
|
||||||
|
@ -118,7 +141,14 @@ pub(crate) fn compute_slices_on_asset_event(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut events: EventReader<AssetEvent<Image>>,
|
mut events: EventReader<AssetEvent<Image>>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
ui_nodes: Query<(Entity, &ImageScaleMode, &Node, &UiImage)>,
|
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
|
||||||
|
ui_nodes: Query<(
|
||||||
|
Entity,
|
||||||
|
&ImageScaleMode,
|
||||||
|
&Node,
|
||||||
|
&UiImage,
|
||||||
|
Option<&TextureAtlas>,
|
||||||
|
)>,
|
||||||
) {
|
) {
|
||||||
// We store the asset ids of added/modified image assets
|
// We store the asset ids of added/modified image assets
|
||||||
let added_handles: HashSet<_> = events
|
let added_handles: HashSet<_> = events
|
||||||
|
@ -132,11 +162,18 @@ pub(crate) fn compute_slices_on_asset_event(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We recompute the sprite slices for sprite entities with a matching asset handle id
|
// We recompute the sprite slices for sprite entities with a matching asset handle id
|
||||||
for (entity, scale_mode, ui_node, image) in &ui_nodes {
|
for (entity, scale_mode, ui_node, image, atlas) in &ui_nodes {
|
||||||
if !added_handles.contains(&image.texture.id()) {
|
if !added_handles.contains(&image.texture.id()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(slices) = compute_texture_slices(ui_node.size(), scale_mode, image, &images) {
|
if let Some(slices) = compute_texture_slices(
|
||||||
|
ui_node.size(),
|
||||||
|
scale_mode,
|
||||||
|
image,
|
||||||
|
&images,
|
||||||
|
atlas,
|
||||||
|
&atlas_layouts,
|
||||||
|
) {
|
||||||
commands.entity(entity).insert(slices);
|
commands.entity(entity).insert(slices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,13 +184,32 @@ pub(crate) fn compute_slices_on_asset_event(
|
||||||
pub(crate) fn compute_slices_on_image_change(
|
pub(crate) fn compute_slices_on_image_change(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
|
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
|
||||||
changed_nodes: Query<
|
changed_nodes: Query<
|
||||||
(Entity, &ImageScaleMode, &Node, &UiImage),
|
(
|
||||||
Or<(Changed<ImageScaleMode>, Changed<UiImage>, Changed<Node>)>,
|
Entity,
|
||||||
|
&ImageScaleMode,
|
||||||
|
&Node,
|
||||||
|
&UiImage,
|
||||||
|
Option<&TextureAtlas>,
|
||||||
|
),
|
||||||
|
Or<(
|
||||||
|
Changed<ImageScaleMode>,
|
||||||
|
Changed<UiImage>,
|
||||||
|
Changed<Node>,
|
||||||
|
Changed<TextureAtlas>,
|
||||||
|
)>,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
for (entity, scale_mode, ui_node, image) in &changed_nodes {
|
for (entity, scale_mode, ui_node, image, atlas) in &changed_nodes {
|
||||||
if let Some(slices) = compute_texture_slices(ui_node.size(), scale_mode, image, &images) {
|
if let Some(slices) = compute_texture_slices(
|
||||||
|
ui_node.size(),
|
||||||
|
scale_mode,
|
||||||
|
image,
|
||||||
|
&images,
|
||||||
|
atlas,
|
||||||
|
&atlas_layouts,
|
||||||
|
) {
|
||||||
commands.entity(entity).insert(slices);
|
commands.entity(entity).insert(slices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -405,6 +405,7 @@ Example | Description
|
||||||
[UI Material](../examples/ui/ui_material.rs) | Demonstrates creating and using custom Ui materials
|
[UI Material](../examples/ui/ui_material.rs) | Demonstrates creating and using custom Ui materials
|
||||||
[UI Scaling](../examples/ui/ui_scaling.rs) | Illustrates how to scale the UI
|
[UI Scaling](../examples/ui/ui_scaling.rs) | Illustrates how to scale the UI
|
||||||
[UI Texture Atlas](../examples/ui/ui_texture_atlas.rs) | Illustrates how to use TextureAtlases in UI
|
[UI Texture Atlas](../examples/ui/ui_texture_atlas.rs) | Illustrates how to use TextureAtlases in UI
|
||||||
|
[UI Texture Atlas Slice](../examples/ui/ui_texture_atlas_slice.rs) | Illustrates how to use 9 Slicing for TextureAtlases in UI
|
||||||
[UI Texture Slice](../examples/ui/ui_texture_slice.rs) | Illustrates how to use 9 Slicing in UI
|
[UI Texture Slice](../examples/ui/ui_texture_slice.rs) | Illustrates how to use 9 Slicing in UI
|
||||||
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
|
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
|
||||||
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
|
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
|
||||||
|
|
115
examples/ui/ui_texture_atlas_slice.rs
Normal file
115
examples/ui/ui_texture_atlas_slice.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
//! This example illustrates how to create buttons with their texture atlases sliced
|
||||||
|
//! and kept in proportion instead of being stretched by the button dimensions
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
color::palettes::css::{GOLD, ORANGE},
|
||||||
|
prelude::*,
|
||||||
|
winit::WinitSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
|
||||||
|
.insert_resource(WinitSettings::desktop_app())
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, button_system)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button_system(
|
||||||
|
mut interaction_query: Query<
|
||||||
|
(&Interaction, &mut TextureAtlas, &Children, &mut UiImage),
|
||||||
|
(Changed<Interaction>, With<Button>),
|
||||||
|
>,
|
||||||
|
mut text_query: Query<&mut Text>,
|
||||||
|
) {
|
||||||
|
for (interaction, mut atlas, children, mut image) in &mut interaction_query {
|
||||||
|
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||||
|
match *interaction {
|
||||||
|
Interaction::Pressed => {
|
||||||
|
text.sections[0].value = "Press".to_string();
|
||||||
|
atlas.index = (atlas.index + 1) % 30;
|
||||||
|
image.color = GOLD.into();
|
||||||
|
}
|
||||||
|
Interaction::Hovered => {
|
||||||
|
text.sections[0].value = "Hover".to_string();
|
||||||
|
image.color = ORANGE.into();
|
||||||
|
}
|
||||||
|
Interaction::None => {
|
||||||
|
text.sections[0].value = "Button".to_string();
|
||||||
|
image.color = Color::WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||||
|
) {
|
||||||
|
let texture_handle = asset_server.load("textures/fantasy_ui_borders/border_sheet.png");
|
||||||
|
let atlas_layout = TextureAtlasLayout::from_grid(UVec2::new(50, 50), 6, 6, None, None);
|
||||||
|
let atlas_layout_handle = texture_atlases.add(atlas_layout);
|
||||||
|
|
||||||
|
let slicer = TextureSlicer {
|
||||||
|
border: BorderRect::square(22.0),
|
||||||
|
center_scale_mode: SliceScaleMode::Stretch,
|
||||||
|
sides_scale_mode: SliceScaleMode::Stretch,
|
||||||
|
max_corner_scale: 1.0,
|
||||||
|
};
|
||||||
|
// ui camera
|
||||||
|
commands.spawn(Camera2dBundle::default());
|
||||||
|
commands
|
||||||
|
.spawn(NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|parent| {
|
||||||
|
for (idx, [w, h]) in [
|
||||||
|
(0, [150.0, 150.0]),
|
||||||
|
(7, [300.0, 150.0]),
|
||||||
|
(13, [150.0, 300.0]),
|
||||||
|
] {
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
ButtonBundle {
|
||||||
|
style: Style {
|
||||||
|
width: Val::Px(w),
|
||||||
|
height: Val::Px(h),
|
||||||
|
// horizontally center child text
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
// vertically center child text
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
margin: UiRect::all(Val::Px(20.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
image: texture_handle.clone().into(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ImageScaleMode::Sliced(slicer.clone()),
|
||||||
|
TextureAtlas {
|
||||||
|
index: idx,
|
||||||
|
layout: atlas_layout_handle.clone(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent.spawn(TextBundle::from_section(
|
||||||
|
"Button",
|
||||||
|
TextStyle {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 40.0,
|
||||||
|
color: Color::srgb(0.9, 0.9, 0.9),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
//! This example illustrates how to create a button that has its image sliced
|
//! This example illustrates how to create buttons with their textures sliced
|
||||||
//! and kept in proportion instead of being stretched by the button dimensions
|
//! and kept in proportion instead of being stretched by the button dimensions
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
|
Loading…
Reference in a new issue