Skip to content

Commit

Permalink
UI texture atlas support (#8822)
Browse files Browse the repository at this point in the history
# Objective

This adds support for using texture atlas sprites in UI. From
discussions today in the ui-dev discord it seems this is a much wanted
feature.

This was previously attempted in #5070 by @ManevilleF however that was
blocked #5103. This work can be easily modified to support #5103 changes
after that merges.

## Solution

I created a new UI bundle that reuses the existing texture atlas
infrastructure. I create a new atlas image component to prevent it from
being drawn by the existing non-UI systems and to remove unused
parameters.

In extract I added new system to calculate the required values for the
texture atlas image, this extracts into the same resource as the
existing UI Image and Text components.

This should have minimal performance impact because if texture atlas is
not present then the exact same code path is followed. Also there should
be no unintended behavior changes because without the new components the
existing systems write the extract same resulting data.

I also added an example showing the sprite working and a system to
advance the animation on space bar presses.

Naming is hard and I would accept any feedback on the bundle name! 

---

## Changelog

>  Added TextureAtlasImageBundle

---------

Co-authored-by: ickshonpe <[email protected]>
  • Loading branch information
mwbryant and ickshonpe authored Jun 19, 2023
1 parent 7fc6db3 commit 8b5bf42
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 15 deletions.
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1943,6 +1943,16 @@ description = "Illustrates how to scale the UI"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "ui_texture_atlas"
path = "examples/ui/ui_texture_atlas.rs"

[package.metadata.example.ui_texture_atlas]
name = "UI Texture Atlas"
description = "Illustrates how to use TextureAtlases in UI"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "viewport_debug"
path = "examples/ui/viewport_debug.rs"
Expand Down
8 changes: 6 additions & 2 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,12 @@ impl Plugin for UiPlugin {
.ambiguous_with(widget::text_system);

system
})
.add_systems(
});
app.add_systems(
PostUpdate,
widget::update_atlas_content_size_system.before(UiSystem::Layout),
);
app.add_systems(
PostUpdate,
(
ui_layout_system
Expand Down
49 changes: 48 additions & 1 deletion crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
use crate::{
widget::{Button, TextFlags, UiImageSize},
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
ZIndex,
UiTextureAtlasImage, ZIndex,
};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render::{
prelude::{Color, ComputedVisibility},
view::Visibility,
};
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
use bevy_text::{Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle};
use bevy_transform::prelude::{GlobalTransform, Transform};
Expand Down Expand Up @@ -109,6 +111,51 @@ pub struct ImageBundle {
pub z_index: ZIndex,
}

/// A UI node that is a texture atlas sprite
#[derive(Bundle, Debug, Default)]
pub struct AtlasImageBundle {
/// Describes the logical size of the node
///
/// This field is automatically managed by the UI layout system.
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
pub node: Node,
/// Styles which control the layout (size and position) of the node and it's children
/// In some cases these styles also affect how the node drawn/painted.
pub style: Style,
/// The calculated size based on the given image
pub calculated_size: ContentSize,
/// The background color, which serves as a "fill" for this node
///
/// Combines with `UiImage` to tint the provided image.
pub background_color: BackgroundColor,
/// A handle to the texture atlas to use for this Ui Node
pub texture_atlas: Handle<TextureAtlas>,
/// The descriptor for which sprite to use from the given texture atlas
pub texture_atlas_image: UiTextureAtlasImage,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// The size of the image in pixels
///
/// This field is set automatically
pub image_size: UiImageSize,
/// The transform of the node
///
/// This field is automatically managed by the UI layout system.
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
pub transform: Transform,
/// The global transform of the node
///
/// This field is automatically managed by the UI layout system.
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
}

#[cfg(feature = "bevy_text")]
/// A UI node that is text
#[derive(Bundle, Debug)]
Expand Down
102 changes: 92 additions & 10 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bevy_window::{PrimaryWindow, Window};
pub use pipeline::*;
pub use render_pass::*;

use crate::UiTextureAtlasImage;
use crate::{
prelude::UiCameraConfig, BackgroundColor, BorderColor, CalculatedClip, Node, UiImage, UiStack,
};
Expand Down Expand Up @@ -82,6 +83,7 @@ pub fn build_ui_render(app: &mut App) {
extract_default_ui_camera_view::<Camera2d>,
extract_default_ui_camera_view::<Camera3d>,
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
extract_atlas_uinodes.after(RenderUiSystem::ExtractNode),
extract_uinode_borders.after(RenderUiSystem::ExtractNode),
#[cfg(feature = "bevy_text")]
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
Expand Down Expand Up @@ -166,6 +168,83 @@ pub struct ExtractedUiNodes {
pub uinodes: Vec<ExtractedUiNode>,
}

pub fn extract_atlas_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
images: Extract<Res<Assets<Image>>>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,

ui_stack: Extract<Res<UiStack>>,
uinode_query: Extract<
Query<
(
&Node,
&GlobalTransform,
&BackgroundColor,
&ComputedVisibility,
Option<&CalculatedClip>,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
),
Without<UiImage>,
>,
>,
) {
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((uinode, transform, color, visibility, clip, texture_atlas_handle, atlas_image)) =
uinode_query.get(*entity)
{
// Skip invisible and completely transparent nodes
if !visibility.is_visible() || color.0.a() == 0.0 {
continue;
}

let (mut atlas_rect, mut atlas_size, image) =
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
let atlas_rect = *texture_atlas
.textures
.get(atlas_image.index)
.unwrap_or_else(|| {
panic!(
"Atlas index {:?} does not exist for texture atlas handle {:?}.",
atlas_image.index,
texture_atlas_handle.id(),
)
});
(
atlas_rect,
texture_atlas.size,
texture_atlas.texture.clone(),
)
} else {
// Atlas not present in assets resource (should this warn the user?)
continue;
};

// Skip loading images
if !images.contains(&image) {
continue;
}

let scale = uinode.size() / atlas_rect.size();
atlas_rect.min *= scale;
atlas_rect.max *= scale;
atlas_size *= scale;

extracted_uinodes.uinodes.push(ExtractedUiNode {
stack_index,
transform: transform.compute_matrix(),
color: color.0,
rect: atlas_rect,
clip: clip.map(|clip| clip.clip),
image,
atlas_size: Some(atlas_size),
flip_x: atlas_image.flip_x,
flip_y: atlas_image.flip_y,
});
}
}
}

fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
match value {
Val::Auto => 0.,
Expand Down Expand Up @@ -288,14 +367,17 @@ pub fn extract_uinodes(
images: Extract<Res<Assets<Image>>>,
ui_stack: Extract<Res<UiStack>>,
uinode_query: Extract<
Query<(
&Node,
&GlobalTransform,
&BackgroundColor,
Option<&UiImage>,
&ComputedVisibility,
Option<&CalculatedClip>,
)>,
Query<
(
&Node,
&GlobalTransform,
&BackgroundColor,
Option<&UiImage>,
&ComputedVisibility,
Option<&CalculatedClip>,
),
Without<UiTextureAtlasImage>,
>,
>,
) {
extracted_uinodes.uinodes.clear();
Expand Down Expand Up @@ -327,13 +409,13 @@ pub fn extract_uinodes(
min: Vec2::ZERO,
max: uinode.calculated_size,
},
clip: clip.map(|clip| clip.clip),
image,
atlas_size: None,
clip: clip.map(|clip| clip.clip),
flip_x,
flip_y,
});
}
};
}
}

Expand Down
12 changes: 12 additions & 0 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,18 @@ impl From<Color> for BackgroundColor {
}
}

/// The atlas sprite to be used in a UI Texture Atlas Node
#[derive(Component, Clone, Debug, Reflect, FromReflect, Default)]
#[reflect(Component, Default)]
pub struct UiTextureAtlasImage {
/// Texture index in the TextureAtlas
pub index: usize,
/// Whether to flip the sprite in the X axis
pub flip_x: bool,
/// Whether to flip the sprite in the Y axis
pub flip_y: bool,
}

/// The border color of the UI node.
#[derive(Component, Copy, Clone, Debug, Reflect, FromReflect)]
#[reflect(FromReflect, Component, Default)]
Expand Down
45 changes: 43 additions & 2 deletions crates/bevy_ui/src/widget/image.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage};
use bevy_asset::Assets;
use crate::{
measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiTextureAtlasImage,
};
use bevy_asset::{Assets, Handle};
#[cfg(feature = "bevy_text")]
use bevy_ecs::query::Without;
use bevy_ecs::{
Expand All @@ -11,6 +13,7 @@ use bevy_ecs::{
use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect};
use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
use bevy_text::Text;

Expand Down Expand Up @@ -89,3 +92,41 @@ pub fn update_image_content_size_system(
}
}
}

/// Updates content size of the node based on the texture atlas sprite
pub fn update_atlas_content_size_system(
atlases: Res<Assets<TextureAtlas>>,
#[cfg(feature = "bevy_text")] mut atlas_query: Query<
(
&mut ContentSize,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
&mut UiImageSize,
),
(With<Node>, Without<Text>, Without<UiImage>),
>,
#[cfg(not(feature = "bevy_text"))] mut atlas_query: Query<
(
&mut ContentSize,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
&mut UiImageSize,
),
(With<Node>, Without<UiImage>),
>,
) {
for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query {
if let Some(atlas) = atlases.get(atlas) {
let texture_rect = atlas.textures[atlas_image.index];
let size = Vec2::new(
texture_rect.max.x - texture_rect.min.x,
texture_rect.max.y - texture_rect.min.y,
);
// Update only if size has changed to avoid needless layout calculations
if size != image_size.size {
image_size.size = size;
content_size.set(ImageMeasure { size });
}
}
}
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ Example | Description
[Transparency UI](../examples/ui/transparency_ui.rs) | Demonstrates transparency for UI
[UI](../examples/ui/ui.rs) | Illustrates various features of Bevy 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 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
[Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality.
Expand Down
Loading

0 comments on commit 8b5bf42

Please sign in to comment.