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 #3792

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,10 @@ path = "examples/ui/text_debug.rs"
name = "ui"
path = "examples/ui/ui.rs"

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

# Window
[[example]]
name = "clear_color"
Expand Down
68 changes: 67 additions & 1 deletion crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use crate::{
widget::{Button, ImageMode},
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI,
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, UiTextureAtlas,
CAMERA_UI,
};
use bevy_ecs::bundle::Bundle;
use bevy_render::{
Expand Down Expand Up @@ -54,6 +55,29 @@ pub struct ImageBundle {
pub visibility: Visibility,
}

/// A UI node that is an image with texture sheet
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A UI node that is an image with texture sheet
/// A UI node that displays an image stored in a texture sheet

#[derive(Bundle, Clone, Debug, Default)]
pub struct ImageSheetBundle {
/// Describes the size of the node
pub node: Node,
/// Describes the style including flexbox settings
pub style: Style,
/// Configures how the image should scale
pub image_mode: ImageMode,
/// The calculated size based on the given image
pub calculated_size: CalculatedSize,
/// The color of the node
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should describe how the blending is done.

pub color: UiColor,
/// The texture atlas of the node
pub texture_atlas: UiTextureAtlas,
/// The transform of the node
pub transform: Transform,
/// The global transform of the node
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
}

/// A UI node that is text
#[derive(Bundle, Clone, Debug)]
pub struct TextBundle {
Expand Down Expand Up @@ -132,6 +156,48 @@ impl Default for ButtonBundle {
}
}

/// A UI node that is a button with a texture sheet
#[derive(Bundle, Clone, Debug)]
pub struct ButtonSheetBundle {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the creation of a dedicated bundle here: this should be done compositionally.

/// Describes the size of the node
pub node: Node,
/// Marker component that signals this node is a button
pub button: Button,
/// Describes the style including flexbox settings
pub style: Style,
/// Describes whether and how the button has been interacted with by the input
pub interaction: Interaction,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// The color of the node
pub color: UiColor,
/// The texture atlas of the node
pub texture_atlas: UiTextureAtlas,
/// The transform of the node
pub transform: Transform,
/// The global transform of the node
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
}

impl Default for ButtonSheetBundle {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the derive work fine here?

fn default() -> Self {
Self {
button: Button,
interaction: Default::default(),
focus_policy: Default::default(),
node: Default::default(),
style: Default::default(),
color: Default::default(),
texture_atlas: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
}
}
}

/// The camera that is needed to see UI elements
#[derive(Bundle, Debug)]
pub struct UiCameraBundle {
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl Plugin for UiPlugin {
.register_type::<Style>()
.register_type::<UiColor>()
.register_type::<UiImage>()
.register_type::<UiTextureAtlas>()
.register_type::<Val>()
.register_type::<widget::Button>()
.register_type::<widget::ImageMode>()
Expand All @@ -85,6 +86,10 @@ impl Plugin for UiPlugin {
CoreStage::PostUpdate,
widget::image_node_system.before(UiSystem::Flex),
)
.add_system_to_stage(
CoreStage::PostUpdate,
widget::image_sheet_node_system.before(UiSystem::Flex),
)
.add_system_to_stage(
CoreStage::PostUpdate,
flex_node_system
Expand Down
78 changes: 71 additions & 7 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use bevy_window::Windows;

use bytemuck::{Pod, Zeroable};

use crate::{CalculatedClip, Node, UiColor, UiImage};
use crate::{CalculatedClip, Node, UiColor, UiImage, UiTextureAtlas};

pub mod node {
pub const UI_PASS_DRIVER: &str = "ui_pass_driver";
Expand Down Expand Up @@ -82,12 +82,22 @@ pub fn build_ui_render(app: &mut App) {
.add_system_to_stage(RenderStage::Extract, extract_ui_camera_phases)
.add_system_to_stage(
RenderStage::Extract,
// This system is the first of `RenderStage::Extract` it is responsible for both:
Copy link
Member

@alice-i-cecile alice-i-cecile May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be on doc strings for extract_uinodes and RenderUiSystem instead.

// - Extracting Ui Nodes
// - Clearing the `ExtractedUiNodes` resource between frames.
//
// If you add extra systems they must all come after this system (see the label)
// and only add extra `ExtractedUiNode` to the resource.
extract_uinodes.label(RenderUiSystem::ExtractNode),
)
.add_system_to_stage(
RenderStage::Extract,
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
)
.add_system_to_stage(
RenderStage::Extract,
extract_atlas_uinodes.after(RenderUiSystem::ExtractNode),
)
.add_system_to_stage(RenderStage::Prepare, prepare_uinodes)
.add_system_to_stage(RenderStage::Queue, queue_uinodes)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<TransparentUi>);
Expand Down Expand Up @@ -148,9 +158,11 @@ pub fn extract_uinodes(
)>,
) {
let mut extracted_uinodes = render_world.get_resource_mut::<ExtractedUiNodes>().unwrap();
// Resource clearing
extracted_uinodes.uinodes.clear();
for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() {
if !visibility.is_visible {
// Skips if the node is not visible or if its size is set to zero (e.g. when a parent is set to `Display::None`)
if !visibility.is_visible || uinode.size == Vec2::ZERO {
continue;
}
let image = image.0.clone_weak();
Expand All @@ -172,6 +184,61 @@ pub fn extract_uinodes(
}
}

pub fn extract_atlas_uinodes(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is public, and thus needs doc strings.

mut render_world: ResMut<RenderWorld>,
texture_atlases: Res<Assets<TextureAtlas>>,
images: Res<Assets<Image>>,
uinode_query: Query<(
&Node,
&GlobalTransform,
Option<&UiColor>,
&UiTextureAtlas,
&Visibility,
Option<&CalculatedClip>,
)>,
) {
let mut extracted_uinodes = render_world.get_resource_mut::<ExtractedUiNodes>().unwrap();
for (uinode, transform, color, ui_atlas, visibility, clip) in uinode_query.iter() {
// Skips if the node is not visible or if its size is set to zero (e.g. when a parent is set to `Display::None`)
if !visibility.is_visible || uinode.size == Vec2::ZERO {
continue;
}
let atlas = texture_atlases
.get(ui_atlas.atlas.clone_weak())
.unwrap_or_else(|| {
panic!(
"Failed to retrieve `TextureAtlas` from handle {:?}",
ui_atlas.atlas
)
});
// Skip loading images
if !images.contains(atlas.texture.clone_weak()) {
continue;
}
let image = atlas.texture.clone_weak();
let atlas_size = Some(atlas.size);
let color = color.map_or(Color::default(), |c| c.0);
let rect = atlas
.textures
.get(ui_atlas.index)
.copied()
.unwrap_or_else(|| {
panic!(
"TextureAtlas {:?} as no texture at index {}",
ui_atlas.atlas, ui_atlas.index
)
});
extracted_uinodes.uinodes.push(ExtractedUiNode {
transform: transform.compute_matrix(),
color,
rect,
image,
atlas_size,
clip: clip.map(|clip| clip.clip),
});
}
}

pub fn extract_text_uinodes(
mut render_world: ResMut<RenderWorld>,
texture_atlases: Res<Assets<TextureAtlas>>,
Expand All @@ -195,11 +262,8 @@ pub fn extract_text_uinodes(
};

for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() {
if !visibility.is_visible {
continue;
}
// Skip if size is set to zero (e.g. when a parent is set to `Display::None`)
if uinode.size == Vec2::ZERO {
// Skips if the node is not visible or if its size is set to zero (e.g. when a parent is set to `Display::None`)
if !visibility.is_visible || uinode.size == Vec2::ZERO {
continue;
}
if let Some(text_layout) = text_pipeline.get_glyphs(&entity) {
Expand Down
17 changes: 17 additions & 0 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use bevy_render::{
color::Color,
texture::{Image, DEFAULT_IMAGE_HANDLE},
};
use bevy_sprite::TextureAtlas;
use serde::{Deserialize, Serialize};
use std::ops::{Add, AddAssign};

Expand Down Expand Up @@ -384,6 +385,22 @@ impl From<Handle<Image>> for UiImage {
}
}

/// The texture atlas of the node
#[derive(Component, Clone, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct UiTextureAtlas {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't feel UI specific at all. Why can't we just use TextureAtlasSprite + Handle<TextureAtlas>?

Whenever possible, we should try to avoid duplicated structures between UI and 2D to avoid divergence and confusion.

/// Texture atlas index
pub index: usize,
/// Texture atlas handle
pub atlas: Handle<TextureAtlas>,
}

impl From<Handle<TextureAtlas>> for UiTextureAtlas {
fn from(atlas: Handle<TextureAtlas>) -> Self {
Self { index: 0, atlas }
}
}

/// The calculated clip of the node
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
Expand Down
28 changes: 27 additions & 1 deletion crates/bevy_ui/src/widget/image.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{CalculatedSize, UiImage};
use crate::{CalculatedSize, UiImage, UiTextureAtlas};
use bevy_asset::Assets;
use bevy_ecs::{
component::Component,
Expand All @@ -9,6 +9,7 @@ use bevy_ecs::{
use bevy_math::Size;
use bevy_reflect::{Reflect, ReflectDeserialize};
use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas;
use serde::{Deserialize, Serialize};

/// Describes how to resize the Image node
Expand Down Expand Up @@ -43,3 +44,28 @@ pub fn image_node_system(
}
}
}

/// Updates calculated size of the node based on the texture atlas provided
pub fn image_sheet_node_system(
texture_atlases: Res<Assets<TextureAtlas>>,
mut query: Query<(&mut CalculatedSize, &UiTextureAtlas), With<ImageMode>>,
) {
for (mut calculated_size, ui_atlas) in query.iter_mut() {
if let Some(atlas) = texture_atlases.get(ui_atlas.atlas.clone_weak()) {
let rect = atlas.textures.get(ui_atlas.index).unwrap_or_else(|| {
panic!(
"TextureAtlas {:?} as no texture at index {}",
ui_atlas.atlas, ui_atlas.index
)
});
let size = Size {
width: rect.width(),
height: rect.height(),
};
// Update only if size has changed to avoid needless layout calculations
if size != calculated_size.size {
calculated_size.size = size;
}
}
}
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ Example | File | Description
Example | File | Description
--- | --- | ---
`button` | [`ui/button.rs`](./ui/button.rs) | Illustrates creating and updating a button
`button_with_atlas` | [`ui/button_with_atlas.rs`](./ui/button_with_atlas.rs) | Illustrates creating and updating a button with a texture atlas
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if we could roll this into the button.rs example, and just create two different buttons there.

`font_atlas_debug` | [`ui/font_atlas_debug.rs`](./ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
`text` | [`ui/text.rs`](./ui/text.rs) | Illustrates creating and updating text
`text_debug` | [`ui/text_debug.rs`](./ui/text_debug.rs) | An example for debugging text layout
Expand Down
Loading