mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
Optional ImageScaleMode (#11780)
> Follow up to #11600 and #10588 @mockersf expressed some [valid concerns](https://github.com/bevyengine/bevy/pull/11600#issuecomment-1932796498) about the current system this PR attempts to fix: The `ComputedTextureSlices` reacts to asset change in both `bevy_sprite` and `bevy_ui`, meaning that if the `ImageScaleMode` is inserted by default in the bundles, we will iterate through most 2d items every time an asset is updated. # Solution - `ImageScaleMode` only has two variants: `Sliced` and `Tiled`. I removed the `Stretched` default - `ImageScaleMode` is no longer part of any bundle, but the relevant bundles explain that this additional component can be inserted This way, the *absence* of `ImageScaleMode` means the image will be stretched, and its *presence* will include the entity to the various slicing systems Optional components in bundles would make this more straigthfoward # Additional work Should I add new bundles with the `ImageScaleMode` component ?
This commit is contained in:
parent
0ebba278f7
commit
e0c296ee14
8 changed files with 77 additions and 83 deletions
|
@ -1,4 +1,4 @@
|
|||
use crate::{texture_atlas::TextureAtlas, ImageScaleMode, Sprite};
|
||||
use crate::{Sprite, TextureAtlas};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_render::{
|
||||
|
@ -8,12 +8,16 @@ use bevy_render::{
|
|||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
/// A [`Bundle`] of components for drawing a single sprite from an image.
|
||||
///
|
||||
/// # Extra behaviours
|
||||
///
|
||||
/// You may add the following components to enable additional behaviours
|
||||
/// - [`ImageScaleMode`](crate::ImageScaleMode) to enable either slicing or tiling of the texture
|
||||
/// - [`TextureAtlas`] to draw specific sections of a sprite sheet, (See [`SpriteSheetBundle`])
|
||||
#[derive(Bundle, Clone, Default)]
|
||||
pub struct SpriteBundle {
|
||||
/// Specifies the rendering properties of the sprite, such as color tint and flip.
|
||||
pub sprite: Sprite,
|
||||
/// Controls how the image is altered when scaled.
|
||||
pub scale_mode: ImageScaleMode,
|
||||
/// The local transform of the sprite, relative to its parent.
|
||||
pub transform: Transform,
|
||||
/// The absolute transform of the sprite. This should generally not be written to directly.
|
||||
|
@ -41,8 +45,6 @@ pub struct SpriteBundle {
|
|||
pub struct SpriteSheetBundle {
|
||||
/// Specifies the rendering properties of the sprite, such as color tint and flip.
|
||||
pub sprite: Sprite,
|
||||
/// Controls how the image is altered when scaled.
|
||||
pub scale_mode: ImageScaleMode,
|
||||
/// The local transform of the sprite, relative to its parent.
|
||||
pub transform: Transform,
|
||||
/// The absolute transform of the sprite. This should generally not be written to directly.
|
||||
|
|
|
@ -32,12 +32,9 @@ pub struct Sprite {
|
|||
}
|
||||
|
||||
/// Controls how the image is altered when scaled.
|
||||
#[derive(Component, Debug, Default, Clone, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
#[derive(Component, Debug, Clone, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub enum ImageScaleMode {
|
||||
/// The entire texture stretches when its dimensions change. This is the default option.
|
||||
#[default]
|
||||
Stretched,
|
||||
/// The texture will be cut in 9 slices, keeping the texture in proportions on resize
|
||||
Sliced(TextureSlicer),
|
||||
/// The texture will be repeated if stretched beyond `stretched_value`
|
||||
|
|
|
@ -8,7 +8,7 @@ use bevy_render::texture::Image;
|
|||
use bevy_transform::prelude::*;
|
||||
use bevy_utils::HashSet;
|
||||
|
||||
/// Component storing texture slices for sprite entities with a tiled or sliced [`ImageScaleMode`]
|
||||
/// Component storing texture slices for sprite entities with a [`ImageScaleMode`]
|
||||
///
|
||||
/// This component is automatically inserted and updated
|
||||
#[derive(Debug, Clone, Component)]
|
||||
|
@ -62,9 +62,7 @@ impl ComputedTextureSlices {
|
|||
/// 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.
|
||||
///
|
||||
/// Returns `None` if either:
|
||||
/// - The scale mode is [`ImageScaleMode::Stretched`]
|
||||
/// - The image asset is not loaded
|
||||
/// Returns `None` if the image asset is not loaded
|
||||
#[must_use]
|
||||
fn compute_sprite_slices(
|
||||
sprite: &Sprite,
|
||||
|
@ -72,9 +70,6 @@ fn compute_sprite_slices(
|
|||
image_handle: &Handle<Image>,
|
||||
images: &Assets<Image>,
|
||||
) -> Option<ComputedTextureSlices> {
|
||||
if let ImageScaleMode::Stretched = scale_mode {
|
||||
return None;
|
||||
}
|
||||
let image_size = images.get(image_handle).map(|i| {
|
||||
Vec2::new(
|
||||
i.texture_descriptor.size.width as f32,
|
||||
|
@ -82,7 +77,6 @@ fn compute_sprite_slices(
|
|||
)
|
||||
})?;
|
||||
let slices = match scale_mode {
|
||||
ImageScaleMode::Stretched => unreachable!(),
|
||||
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(
|
||||
sprite.rect.unwrap_or(Rect {
|
||||
min: Vec2::ZERO,
|
||||
|
@ -110,7 +104,7 @@ fn compute_sprite_slices(
|
|||
}
|
||||
|
||||
/// System reacting to added or modified [`Image`] handles, and recompute sprite slices
|
||||
/// on matching sprite entities
|
||||
/// on matching sprite entities with a [`ImageScaleMode`] component
|
||||
pub(crate) fn compute_slices_on_asset_event(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<AssetEvent<Image>>,
|
||||
|
@ -140,6 +134,7 @@ pub(crate) fn compute_slices_on_asset_event(
|
|||
}
|
||||
|
||||
/// System reacting to changes on relevant sprite bundle components to compute the sprite slices
|
||||
/// on matching sprite entities with a [`ImageScaleMode`] component
|
||||
pub(crate) fn compute_slices_on_sprite_change(
|
||||
mut commands: Commands,
|
||||
images: Res<Assets<Image>>,
|
||||
|
|
|
@ -13,7 +13,7 @@ use bevy_render::{
|
|||
prelude::Color,
|
||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||
};
|
||||
use bevy_sprite::{ImageScaleMode, TextureAtlas};
|
||||
use bevy_sprite::TextureAtlas;
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_text::{BreakLineOn, JustifyText, Text, TextLayoutInfo, TextSection, TextStyle};
|
||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||
|
@ -76,6 +76,11 @@ impl Default for NodeBundle {
|
|||
}
|
||||
|
||||
/// A UI node that is an image
|
||||
///
|
||||
/// # 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
|
||||
#[derive(Bundle, Debug, Default)]
|
||||
pub struct ImageBundle {
|
||||
/// Describes the logical size of the node
|
||||
|
@ -95,8 +100,6 @@ pub struct ImageBundle {
|
|||
///
|
||||
/// This component is set automatically
|
||||
pub image_size: UiImageSize,
|
||||
/// Controls how the image is altered when scaled.
|
||||
pub scale_mode: ImageScaleMode,
|
||||
/// Whether this node should block interaction with lower nodes
|
||||
pub focus_policy: FocusPolicy,
|
||||
/// The transform of the node
|
||||
|
@ -288,6 +291,11 @@ where
|
|||
}
|
||||
|
||||
/// A UI node that is a button
|
||||
///
|
||||
/// # 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
|
||||
#[derive(Bundle, Clone, Debug)]
|
||||
pub struct ButtonBundle {
|
||||
/// Describes the logical size of the node
|
||||
|
@ -309,8 +317,6 @@ pub struct ButtonBundle {
|
|||
pub border_color: BorderColor,
|
||||
/// The image of the node
|
||||
pub image: UiImage,
|
||||
/// Controls how the image is altered when scaled.
|
||||
pub scale_mode: ImageScaleMode,
|
||||
/// The transform of the node
|
||||
///
|
||||
/// This component is automatically managed by the UI layout system.
|
||||
|
@ -347,7 +353,6 @@ impl Default for ButtonBundle {
|
|||
inherited_visibility: Default::default(),
|
||||
view_visibility: Default::default(),
|
||||
z_index: Default::default(),
|
||||
scale_mode: ImageScaleMode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,9 +77,7 @@ impl ComputedTextureSlices {
|
|||
/// 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.
|
||||
///
|
||||
/// Returns `None` if either:
|
||||
/// - The scale mode is [`ImageScaleMode::Stretched`]
|
||||
/// - The image asset is not loaded
|
||||
/// Returns `None` if the image asset is not loaded
|
||||
#[must_use]
|
||||
fn compute_texture_slices(
|
||||
draw_area: Vec2,
|
||||
|
@ -87,9 +85,6 @@ fn compute_texture_slices(
|
|||
image_handle: &UiImage,
|
||||
images: &Assets<Image>,
|
||||
) -> Option<ComputedTextureSlices> {
|
||||
if let ImageScaleMode::Stretched = scale_mode {
|
||||
return None;
|
||||
}
|
||||
let image_size = images.get(&image_handle.texture).map(|i| {
|
||||
Vec2::new(
|
||||
i.texture_descriptor.size.width as f32,
|
||||
|
@ -101,7 +96,6 @@ fn compute_texture_slices(
|
|||
max: image_size,
|
||||
};
|
||||
let slices = match scale_mode {
|
||||
ImageScaleMode::Stretched => unreachable!(),
|
||||
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, Some(draw_area)),
|
||||
ImageScaleMode::Tiled {
|
||||
tile_x,
|
||||
|
@ -120,7 +114,7 @@ fn compute_texture_slices(
|
|||
}
|
||||
|
||||
/// System reacting to added or modified [`Image`] handles, and recompute sprite slices
|
||||
/// on matching sprite entities
|
||||
/// on matching sprite entities with a [`ImageScaleMode`] component
|
||||
pub(crate) fn compute_slices_on_asset_event(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<AssetEvent<Image>>,
|
||||
|
@ -157,6 +151,7 @@ pub(crate) fn compute_slices_on_asset_event(
|
|||
}
|
||||
|
||||
/// System reacting to changes on relevant sprite bundle components to compute the sprite slices
|
||||
/// on matching sprite entities with a [`ImageScaleMode`] component
|
||||
pub(crate) fn compute_slices_on_image_change(
|
||||
mut commands: Commands,
|
||||
images: Res<Assets<Image>>,
|
||||
|
|
|
@ -25,89 +25,85 @@ fn spawn_sprites(
|
|||
) {
|
||||
let cases = [
|
||||
// Reference sprite
|
||||
(
|
||||
"Original texture",
|
||||
style.clone(),
|
||||
Vec2::splat(100.0),
|
||||
ImageScaleMode::default(),
|
||||
),
|
||||
("Original texture", style.clone(), Vec2::splat(100.0), None),
|
||||
// Scaled regular sprite
|
||||
(
|
||||
"Stretched texture",
|
||||
style.clone(),
|
||||
Vec2::new(100.0, 200.0),
|
||||
ImageScaleMode::default(),
|
||||
None,
|
||||
),
|
||||
// Stretched Scaled sliced sprite
|
||||
(
|
||||
"Stretched and sliced",
|
||||
style.clone(),
|
||||
Vec2::new(100.0, 200.0),
|
||||
ImageScaleMode::Sliced(TextureSlicer {
|
||||
Some(ImageScaleMode::Sliced(TextureSlicer {
|
||||
border: BorderRect::square(slice_border),
|
||||
center_scale_mode: SliceScaleMode::Stretch,
|
||||
..default()
|
||||
}),
|
||||
})),
|
||||
),
|
||||
// Scaled sliced sprite
|
||||
(
|
||||
"Sliced and Tiled",
|
||||
style.clone(),
|
||||
Vec2::new(100.0, 200.0),
|
||||
ImageScaleMode::Sliced(TextureSlicer {
|
||||
Some(ImageScaleMode::Sliced(TextureSlicer {
|
||||
border: BorderRect::square(slice_border),
|
||||
center_scale_mode: SliceScaleMode::Tile { stretch_value: 0.5 },
|
||||
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 0.2 },
|
||||
..default()
|
||||
}),
|
||||
})),
|
||||
),
|
||||
// Scaled sliced sprite horizontally
|
||||
(
|
||||
"Sliced and Tiled",
|
||||
style.clone(),
|
||||
Vec2::new(300.0, 200.0),
|
||||
ImageScaleMode::Sliced(TextureSlicer {
|
||||
Some(ImageScaleMode::Sliced(TextureSlicer {
|
||||
border: BorderRect::square(slice_border),
|
||||
center_scale_mode: SliceScaleMode::Tile { stretch_value: 0.2 },
|
||||
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 0.3 },
|
||||
..default()
|
||||
}),
|
||||
})),
|
||||
),
|
||||
// Scaled sliced sprite horizontally with max scale
|
||||
(
|
||||
"Sliced and Tiled with corner constraint",
|
||||
style,
|
||||
Vec2::new(300.0, 200.0),
|
||||
ImageScaleMode::Sliced(TextureSlicer {
|
||||
Some(ImageScaleMode::Sliced(TextureSlicer {
|
||||
border: BorderRect::square(slice_border),
|
||||
center_scale_mode: SliceScaleMode::Tile { stretch_value: 0.1 },
|
||||
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 0.2 },
|
||||
max_corner_scale: 0.2,
|
||||
}),
|
||||
})),
|
||||
),
|
||||
];
|
||||
|
||||
for (label, text_style, size, scale_mode) in cases {
|
||||
position.x += 0.5 * size.x;
|
||||
commands
|
||||
.spawn(SpriteBundle {
|
||||
transform: Transform::from_translation(position),
|
||||
texture: texture_handle.clone(),
|
||||
sprite: Sprite {
|
||||
custom_size: Some(size),
|
||||
..default()
|
||||
},
|
||||
scale_mode,
|
||||
let mut cmd = commands.spawn(SpriteBundle {
|
||||
transform: Transform::from_translation(position),
|
||||
texture: texture_handle.clone(),
|
||||
sprite: Sprite {
|
||||
custom_size: Some(size),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
if let Some(scale_mode) = scale_mode {
|
||||
cmd.insert(scale_mode);
|
||||
}
|
||||
cmd.with_children(|builder| {
|
||||
builder.spawn(Text2dBundle {
|
||||
text: Text::from_section(label, text_style).with_justify(JustifyText::Center),
|
||||
transform: Transform::from_xyz(0., -0.5 * size.y - 10., 0.0),
|
||||
text_anchor: bevy::sprite::Anchor::TopCenter,
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn(Text2dBundle {
|
||||
text: Text::from_section(label, text_style).with_justify(JustifyText::Center),
|
||||
transform: Transform::from_xyz(0., -0.5 * size.y - 10., 0.0),
|
||||
text_anchor: bevy::sprite::Anchor::TopCenter,
|
||||
..default()
|
||||
});
|
||||
});
|
||||
});
|
||||
position.x += 0.5 * size.x + gap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,15 +26,17 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
current: 128.0,
|
||||
speed: 50.0,
|
||||
});
|
||||
commands.spawn(SpriteBundle {
|
||||
texture: asset_server.load("branding/icon.png"),
|
||||
scale_mode: ImageScaleMode::Tiled {
|
||||
commands.spawn((
|
||||
SpriteBundle {
|
||||
texture: asset_server.load("branding/icon.png"),
|
||||
..default()
|
||||
},
|
||||
ImageScaleMode::Tiled {
|
||||
tile_x: true,
|
||||
tile_y: true,
|
||||
stretch_value: 0.5, // The image will tile every 128px
|
||||
},
|
||||
..default()
|
||||
});
|
||||
));
|
||||
}
|
||||
|
||||
fn animate(mut sprites: Query<&mut Sprite>, mut state: ResMut<AnimationState>, time: Res<Time>) {
|
||||
|
|
|
@ -58,21 +58,23 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
.with_children(|parent| {
|
||||
for [w, h] in [[150.0, 150.0], [300.0, 150.0], [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)),
|
||||
.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: image.clone().into(),
|
||||
..default()
|
||||
},
|
||||
image: image.clone().into(),
|
||||
scale_mode: ImageScaleMode::Sliced(slicer.clone()),
|
||||
..default()
|
||||
})
|
||||
ImageScaleMode::Sliced(slicer.clone()),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
"Button",
|
||||
|
|
Loading…
Add table
Reference in a new issue