-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
UI Texture Atlas support #3792
Changes from all commits
4b24b3c
b1884f1
18fd64e
a2b24f9
dcb965f
f8c16e1
98b9933
1cc7bd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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::{ | ||
|
@@ -54,6 +55,29 @@ pub struct ImageBundle { | |
pub visibility: Visibility, | ||
} | ||
|
||
/// A UI node that is an image with 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be on doc strings for |
||
// - 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>); | ||
|
@@ -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(); | ||
|
@@ -172,6 +184,61 @@ pub fn extract_uinodes( | |
} | ||
} | ||
|
||
pub fn extract_atlas_uinodes( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>>, | ||
|
@@ -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) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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}; | ||
|
||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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)] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer if we could roll this into the |
||
`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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.