Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI texture atlas support #8822

Merged
merged 25 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6f6a4a6
proof of concept
mwbryant Jun 11, 2023
62a9092
cleanup and add bundle
mwbryant Jun 11, 2023
4c57938
better example behaviour
mwbryant Jun 11, 2023
836b777
CI mistakes
mwbryant Jun 11, 2023
4ea10e0
Update crates/bevy_ui/src/node_bundles.rs
mwbryant Jun 12, 2023
d5c6cd1
CI and requested changes
mwbryant Jun 12, 2023
545a578
removed uv_rect
mwbryant Jun 12, 2023
04c7c44
removed uv_rect
mwbryant Jun 12, 2023
3fece68
Merge branch 'ui_texture_atlas' of https://github.com/mwbryant/bevy i…
mwbryant Jun 12, 2023
770f4a8
revert overflow example... oops
mwbryant Jun 12, 2023
368e904
move to new extract system to reduce complexity
mwbryant Jun 12, 2023
d88c26a
clippy
mwbryant Jun 12, 2023
4fc0737
removed useless structs
mwbryant Jun 12, 2023
99903fe
fixed example
mwbryant Jun 12, 2023
ad99bb2
docs CI
mwbryant Jun 12, 2023
4280685
Added update_content_size_system for atlas sprites
mwbryant Jun 12, 2023
cc0d0a3
scaling optimization
mwbryant Jun 13, 2023
ab2e945
Rename UiTextureAtlasSprite to UiTextureAtlasImage
mwbryant Jun 14, 2023
76829bb
Merge branch 'main' into ui_texture_atlas
mwbryant Jun 15, 2023
6a347ae
github web merge fixes
mwbryant Jun 15, 2023
7ae2cc2
Update examples/ui/ui_texture_atlas.rs
mwbryant Jun 19, 2023
f23d314
Update examples/ui/ui_texture_atlas.rs
mwbryant Jun 19, 2023
5f86a39
Update examples/ui/ui_texture_atlas.rs
mwbryant Jun 19, 2023
453af29
Update examples/ui/ui_texture_atlas.rs
mwbryant Jun 19, 2023
d534719
cargo fmt after accepting github commits
mwbryant Jun 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1923,6 +1923,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

# Window
[[example]]
name = "clear_color"
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 @@ -156,8 +156,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
50 changes: 49 additions & 1 deletion crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

use crate::{
widget::{Button, TextFlags, UiImageSize},
BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
UiTextureAtlasSprite, 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 @@ -105,6 +108,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.
mwbryant marked this conversation as resolved.
Show resolved Hide resolved
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
mwbryant marked this conversation as resolved.
Show resolved Hide resolved
pub texture_atlas_sprite: UiTextureAtlasSprite,
mwbryant marked this conversation as resolved.
Show resolved Hide resolved
/// 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
103 changes: 93 additions & 10 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bevy_window::{PrimaryWindow, Window};
pub use pipeline::*;
pub use render_pass::*;

use crate::UiTextureAtlasSprite;
use crate::{prelude::UiCameraConfig, BackgroundColor, CalculatedClip, Node, UiImage, UiStack};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
Expand Down Expand Up @@ -78,6 +79,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),
#[cfg(feature = "bevy_text")]
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
),
Expand Down Expand Up @@ -161,19 +163,100 @@ 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>,
&UiTextureAtlasSprite,
),
Without<UiImage>,
>,
>,
) {
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((
uinode,
transform,
color,
visibility,
clip,
texture_atlas_handle,
atlas_sprite,
)) = uinode_query.get(*entity)
{
// Skip invisible and completely transparent nodes
if !visibility.is_visible() || color.0.a() == 0.0 {
mwbryant marked this conversation as resolved.
Show resolved Hide resolved
continue;
}

let (atlas_rect, atlas_size, image) =
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
let mut atlas_rect = *texture_atlas
.textures
.get(atlas_sprite.index)
.unwrap_or_else(|| {
panic!(
"Sprite index {:?} does not exist for texture atlas handle {:?}.",
atlas_sprite.index,
texture_atlas_handle.id(),
)
});
let scale = uinode.size() / atlas_rect.size();
mwbryant marked this conversation as resolved.
Show resolved Hide resolved
atlas_rect.min *= scale;
atlas_rect.max *= scale;
let atlas_size = texture_atlas.size * scale;
(atlas_rect, atlas_size, texture_atlas.texture.clone())
} else {
// Atlas not present in assets resource (should this warn the user?)
continue;
mwbryant marked this conversation as resolved.
Show resolved Hide resolved
};

// Skip loading images
if !images.contains(&image) {
continue;
}
mwbryant marked this conversation as resolved.
Show resolved Hide resolved

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_sprite.flip_x,
flip_y: atlas_sprite.flip_y,
});
};
}
}

pub fn extract_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
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<UiTextureAtlasSprite>,
>,
>,
) {
extracted_uinodes.uinodes.clear();
Expand Down Expand Up @@ -204,13 +287,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 UiTextureAtlasSprite {
/// 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 2D texture displayed for this UI node
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(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, UiTextureAtlasSprite,
};
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>,
&UiTextureAtlasSprite,
&mut UiImageSize,
),
(With<Node>, Without<Text>, Without<UiImage>),
>,
#[cfg(not(feature = "bevy_text"))] mut atlas_query: Query<
(
&mut ContentSize,
&Handle<TextureAtlas>,
&UiTextureAtlasSprite,
&mut UiImageSize,
),
(With<Node>, Without<UiImage>),
>,
) {
for (mut content_size, atlas, sprite, mut image_size) in &mut atlas_query {
if let Some(atlas) = atlases.get(atlas) {
let texture_rect = atlas.textures[sprite.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 @@ -349,6 +349,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
[Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality.

Expand Down
Loading