Add stack index to Node
(#9853)
# Objective If we add the stack index to `Node` then we don't need to walk the `UiStack` repeatedly during extraction. ## Solution Add a field `stack_index` to `Node`. Update it in `ui_stack_system`. Iterate queries directly in the UI's extraction systems. ### Benchmarks ``` cargo run --profile stress-test --features trace_tracy --example many_buttons -- --no-text --no-borders ``` frames (yellow this PR, red main): <img width="447" alt="frames-per-second" src="https://github.com/bevyengine/bevy/assets/27962798/385c0ccf-c257-42a2-b736-117542d56eff"> `ui_stack_system`: <img width="585" alt="ui-stack-system" src="https://github.com/bevyengine/bevy/assets/27962798/2916cc44-2887-4c3b-a144-13250d84f7d5"> extract schedule: <img width="469" alt="extract-schedule" src="https://github.com/bevyengine/bevy/assets/27962798/858d4ab4-d99f-48e8-b153-1c92f51e0743"> --- ## Changelog * Added the field `stack_index` to `Node`. * `ui_stack_system` updates `Node::stack_index` after a new `UiStack` is generated. * The UI's extraction functions iterate a query directly rather than walking the `UiStack` and doing lookups.
This commit is contained in:
parent
44928e0df4
commit
563d6e36bb
@ -13,7 +13,7 @@ pub use render_pass::*;
|
|||||||
use crate::Outline;
|
use crate::Outline;
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::UiCameraConfig, BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node,
|
prelude::UiCameraConfig, BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node,
|
||||||
Style, UiImage, UiScale, UiStack, UiTextureAtlasImage, Val,
|
Style, UiImage, UiScale, UiTextureAtlasImage, Val,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
@ -152,7 +152,7 @@ fn get_ui_graph(render_app: &mut App) -> RenderGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtractedUiNode {
|
pub struct ExtractedUiNode {
|
||||||
pub stack_index: usize,
|
pub stack_index: u32,
|
||||||
pub transform: Mat4,
|
pub transform: Mat4,
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
@ -172,7 +172,6 @@ pub fn extract_atlas_uinodes(
|
|||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
images: Extract<Res<Assets<Image>>>,
|
images: Extract<Res<Assets<Image>>>,
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||||
ui_stack: Extract<Res<UiStack>>,
|
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<
|
Query<
|
||||||
(
|
(
|
||||||
@ -189,70 +188,68 @@ pub fn extract_atlas_uinodes(
|
|||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
for (
|
||||||
if let Ok((
|
entity,
|
||||||
entity,
|
uinode,
|
||||||
uinode,
|
transform,
|
||||||
transform,
|
color,
|
||||||
color,
|
view_visibility,
|
||||||
view_visibility,
|
clip,
|
||||||
clip,
|
texture_atlas_handle,
|
||||||
texture_atlas_handle,
|
atlas_image,
|
||||||
atlas_image,
|
) in uinode_query.iter()
|
||||||
)) = uinode_query.get(*entity)
|
{
|
||||||
{
|
// Skip invisible and completely transparent nodes
|
||||||
// Skip invisible and completely transparent nodes
|
if !view_visibility.get() || color.0.is_fully_transparent() {
|
||||||
if !view_visibility.get() || color.0.is_fully_transparent() {
|
continue;
|
||||||
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.insert(
|
|
||||||
entity,
|
|
||||||
ExtractedUiNode {
|
|
||||||
stack_index,
|
|
||||||
transform: transform.compute_matrix(),
|
|
||||||
color: color.0,
|
|
||||||
rect: atlas_rect,
|
|
||||||
clip: clip.map(|clip| clip.clip),
|
|
||||||
image: image.id(),
|
|
||||||
atlas_size: Some(atlas_size),
|
|
||||||
flip_x: atlas_image.flip_x,
|
|
||||||
flip_y: atlas_image.flip_y,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.insert(
|
||||||
|
entity,
|
||||||
|
ExtractedUiNode {
|
||||||
|
stack_index: uinode.stack_index,
|
||||||
|
transform: transform.compute_matrix(),
|
||||||
|
color: color.0,
|
||||||
|
rect: atlas_rect,
|
||||||
|
clip: clip.map(|clip| clip.clip),
|
||||||
|
image: image.id(),
|
||||||
|
atlas_size: Some(atlas_size),
|
||||||
|
flip_x: atlas_image.flip_x,
|
||||||
|
flip_y: atlas_image.flip_y,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +270,6 @@ pub fn extract_uinode_borders(
|
|||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||||
ui_scale: Extract<Res<UiScale>>,
|
ui_scale: Extract<Res<UiScale>>,
|
||||||
ui_stack: Extract<Res<UiStack>>,
|
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<
|
Query<
|
||||||
(
|
(
|
||||||
@ -300,92 +296,84 @@ pub fn extract_uinode_borders(
|
|||||||
// so we have to divide by `UiScale` to get the size of the UI viewport.
|
// so we have to divide by `UiScale` to get the size of the UI viewport.
|
||||||
/ ui_scale.0 as f32;
|
/ ui_scale.0 as f32;
|
||||||
|
|
||||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
for (node, global_transform, style, border_color, parent, view_visibility, clip) in
|
||||||
if let Ok((node, global_transform, style, border_color, parent, view_visibility, clip)) =
|
uinode_query.iter()
|
||||||
uinode_query.get(*entity)
|
{
|
||||||
|
// Skip invisible borders
|
||||||
|
if !view_visibility.get()
|
||||||
|
|| border_color.0.is_fully_transparent()
|
||||||
|
|| node.size().x <= 0.
|
||||||
|
|| node.size().y <= 0.
|
||||||
{
|
{
|
||||||
// Skip invisible borders
|
continue;
|
||||||
if !view_visibility.get()
|
}
|
||||||
|| border_color.0.is_fully_transparent()
|
|
||||||
|| node.size().x <= 0.
|
|
||||||
|| node.size().y <= 0.
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
|
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
|
||||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
||||||
let parent_width = parent
|
let parent_width = parent
|
||||||
.and_then(|parent| node_query.get(parent.get()).ok())
|
.and_then(|parent| node_query.get(parent.get()).ok())
|
||||||
.map(|parent_node| parent_node.size().x)
|
.map(|parent_node| parent_node.size().x)
|
||||||
.unwrap_or(ui_logical_viewport_size.x);
|
.unwrap_or(ui_logical_viewport_size.x);
|
||||||
let left =
|
let left =
|
||||||
resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size);
|
resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size);
|
||||||
let right = resolve_border_thickness(
|
let right =
|
||||||
style.border.right,
|
resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size);
|
||||||
parent_width,
|
let top =
|
||||||
ui_logical_viewport_size,
|
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size);
|
||||||
);
|
let bottom =
|
||||||
let top =
|
resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size);
|
||||||
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size);
|
|
||||||
let bottom = resolve_border_thickness(
|
|
||||||
style.border.bottom,
|
|
||||||
parent_width,
|
|
||||||
ui_logical_viewport_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Calculate the border rects, ensuring no overlap.
|
// Calculate the border rects, ensuring no overlap.
|
||||||
// The border occupies the space between the node's bounding rect and the node's bounding rect inset in each direction by the node's corresponding border value.
|
// The border occupies the space between the node's bounding rect and the node's bounding rect inset in each direction by the node's corresponding border value.
|
||||||
let max = 0.5 * node.size();
|
let max = 0.5 * node.size();
|
||||||
let min = -max;
|
let min = -max;
|
||||||
let inner_min = min + Vec2::new(left, top);
|
let inner_min = min + Vec2::new(left, top);
|
||||||
let inner_max = (max - Vec2::new(right, bottom)).max(inner_min);
|
let inner_max = (max - Vec2::new(right, bottom)).max(inner_min);
|
||||||
let border_rects = [
|
let border_rects = [
|
||||||
// Left border
|
// Left border
|
||||||
Rect {
|
Rect {
|
||||||
min,
|
min,
|
||||||
max: Vec2::new(inner_min.x, max.y),
|
max: Vec2::new(inner_min.x, max.y),
|
||||||
},
|
},
|
||||||
// Right border
|
// Right border
|
||||||
Rect {
|
Rect {
|
||||||
min: Vec2::new(inner_max.x, min.y),
|
min: Vec2::new(inner_max.x, min.y),
|
||||||
max,
|
max,
|
||||||
},
|
},
|
||||||
// Top border
|
// Top border
|
||||||
Rect {
|
Rect {
|
||||||
min: Vec2::new(inner_min.x, min.y),
|
min: Vec2::new(inner_min.x, min.y),
|
||||||
max: Vec2::new(inner_max.x, inner_min.y),
|
max: Vec2::new(inner_max.x, inner_min.y),
|
||||||
},
|
},
|
||||||
// Bottom border
|
// Bottom border
|
||||||
Rect {
|
Rect {
|
||||||
min: Vec2::new(inner_min.x, inner_max.y),
|
min: Vec2::new(inner_min.x, inner_max.y),
|
||||||
max: Vec2::new(inner_max.x, max.y),
|
max: Vec2::new(inner_max.x, max.y),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let transform = global_transform.compute_matrix();
|
let transform = global_transform.compute_matrix();
|
||||||
|
|
||||||
for edge in border_rects {
|
for edge in border_rects {
|
||||||
if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
|
if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
commands.spawn_empty().id(),
|
commands.spawn_empty().id(),
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
stack_index,
|
stack_index: node.stack_index,
|
||||||
// This translates the uinode's transform to the center of the current border rectangle
|
// This translates the uinode's transform to the center of the current border rectangle
|
||||||
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
|
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
|
||||||
color: border_color.0,
|
color: border_color.0,
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
max: edge.size(),
|
max: edge.size(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
|
||||||
image,
|
|
||||||
atlas_size: None,
|
|
||||||
clip: clip.map(|clip| clip.clip),
|
|
||||||
flip_x: false,
|
|
||||||
flip_y: false,
|
|
||||||
},
|
},
|
||||||
);
|
image,
|
||||||
}
|
atlas_size: None,
|
||||||
|
clip: clip.map(|clip| clip.clip),
|
||||||
|
flip_x: false,
|
||||||
|
flip_y: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -394,7 +382,6 @@ pub fn extract_uinode_borders(
|
|||||||
pub fn extract_uinode_outlines(
|
pub fn extract_uinode_outlines(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
ui_stack: Extract<Res<UiStack>>,
|
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
&Node,
|
&Node,
|
||||||
@ -407,81 +394,75 @@ pub fn extract_uinode_outlines(
|
|||||||
clip_query: Query<&CalculatedClip>,
|
clip_query: Query<&CalculatedClip>,
|
||||||
) {
|
) {
|
||||||
let image = AssetId::<Image>::default();
|
let image = AssetId::<Image>::default();
|
||||||
|
for (node, global_transform, outline, view_visibility, maybe_parent) in uinode_query.iter() {
|
||||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
// Skip invisible outlines
|
||||||
if let Ok((node, global_transform, outline, view_visibility, maybe_parent)) =
|
if !view_visibility.get()
|
||||||
uinode_query.get(*entity)
|
|| outline.color.is_fully_transparent()
|
||||||
|
|| node.outline_width == 0.
|
||||||
{
|
{
|
||||||
// Skip invisible outlines
|
continue;
|
||||||
if !view_visibility.get()
|
}
|
||||||
|| outline.color.is_fully_transparent()
|
|
||||||
|| node.outline_width == 0.
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outline's are drawn outside of a node's borders, so they are clipped using the clipping Rect of their UI node entity's parent.
|
// Outline's are drawn outside of a node's borders, so they are clipped using the clipping Rect of their UI node entity's parent.
|
||||||
let clip = maybe_parent
|
let clip =
|
||||||
.and_then(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip));
|
maybe_parent.and_then(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip));
|
||||||
|
|
||||||
// Calculate the outline rects.
|
// Calculate the outline rects.
|
||||||
let inner_rect =
|
let inner_rect = Rect::from_center_size(Vec2::ZERO, node.size() + 2. * node.outline_offset);
|
||||||
Rect::from_center_size(Vec2::ZERO, node.size() + 2. * node.outline_offset);
|
let outer_rect = inner_rect.inset(node.outline_width());
|
||||||
let outer_rect = inner_rect.inset(node.outline_width());
|
let outline_edges = [
|
||||||
let outline_edges = [
|
// Left edge
|
||||||
// Left edge
|
Rect::new(
|
||||||
Rect::new(
|
outer_rect.min.x,
|
||||||
outer_rect.min.x,
|
outer_rect.min.y,
|
||||||
outer_rect.min.y,
|
inner_rect.min.x,
|
||||||
inner_rect.min.x,
|
outer_rect.max.y,
|
||||||
outer_rect.max.y,
|
),
|
||||||
),
|
// Right edge
|
||||||
// Right edge
|
Rect::new(
|
||||||
Rect::new(
|
inner_rect.max.x,
|
||||||
inner_rect.max.x,
|
outer_rect.min.y,
|
||||||
outer_rect.min.y,
|
outer_rect.max.x,
|
||||||
outer_rect.max.x,
|
outer_rect.max.y,
|
||||||
outer_rect.max.y,
|
),
|
||||||
),
|
// Top edge
|
||||||
// Top edge
|
Rect::new(
|
||||||
Rect::new(
|
inner_rect.min.x,
|
||||||
inner_rect.min.x,
|
outer_rect.min.y,
|
||||||
outer_rect.min.y,
|
inner_rect.max.x,
|
||||||
inner_rect.max.x,
|
inner_rect.min.y,
|
||||||
inner_rect.min.y,
|
),
|
||||||
),
|
// Bottom edge
|
||||||
// Bottom edge
|
Rect::new(
|
||||||
Rect::new(
|
inner_rect.min.x,
|
||||||
inner_rect.min.x,
|
inner_rect.max.y,
|
||||||
inner_rect.max.y,
|
inner_rect.max.x,
|
||||||
inner_rect.max.x,
|
outer_rect.max.y,
|
||||||
outer_rect.max.y,
|
),
|
||||||
),
|
];
|
||||||
];
|
|
||||||
|
|
||||||
let transform = global_transform.compute_matrix();
|
let transform = global_transform.compute_matrix();
|
||||||
|
|
||||||
for edge in outline_edges {
|
for edge in outline_edges {
|
||||||
if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
|
if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
|
||||||
extracted_uinodes.uinodes.insert(
|
extracted_uinodes.uinodes.insert(
|
||||||
commands.spawn_empty().id(),
|
commands.spawn_empty().id(),
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
stack_index,
|
stack_index: node.stack_index,
|
||||||
// This translates the uinode's transform to the center of the current border rectangle
|
// This translates the uinode's transform to the center of the current border rectangle
|
||||||
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
|
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
|
||||||
color: outline.color,
|
color: outline.color,
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
max: edge.size(),
|
max: edge.size(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
|
||||||
image,
|
|
||||||
atlas_size: None,
|
|
||||||
clip,
|
|
||||||
flip_x: false,
|
|
||||||
flip_y: false,
|
|
||||||
},
|
},
|
||||||
);
|
image,
|
||||||
}
|
atlas_size: None,
|
||||||
|
clip,
|
||||||
|
flip_x: false,
|
||||||
|
flip_y: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,7 +471,6 @@ pub fn extract_uinode_outlines(
|
|||||||
pub fn extract_uinodes(
|
pub fn extract_uinodes(
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
images: Extract<Res<Assets<Image>>>,
|
images: Extract<Res<Assets<Image>>>,
|
||||||
ui_stack: Extract<Res<UiStack>>,
|
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<
|
Query<
|
||||||
(
|
(
|
||||||
@ -506,43 +486,41 @@ pub fn extract_uinodes(
|
|||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
for (entity, uinode, transform, color, maybe_image, view_visibility, clip) in
|
||||||
if let Ok((entity, uinode, transform, color, maybe_image, view_visibility, clip)) =
|
uinode_query.iter()
|
||||||
uinode_query.get(*entity)
|
{
|
||||||
{
|
// Skip invisible and completely transparent nodes
|
||||||
// Skip invisible and completely transparent nodes
|
if !view_visibility.get() || color.0.is_fully_transparent() {
|
||||||
if !view_visibility.get() || color.0.is_fully_transparent() {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (image, flip_x, flip_y) = if let Some(image) = maybe_image {
|
||||||
|
// Skip loading images
|
||||||
|
if !images.contains(&image.texture) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
(image.texture.id(), image.flip_x, image.flip_y)
|
||||||
let (image, flip_x, flip_y) = if let Some(image) = maybe_image {
|
} else {
|
||||||
// Skip loading images
|
(AssetId::default(), false, false)
|
||||||
if !images.contains(&image.texture) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
(image.texture.id(), image.flip_x, image.flip_y)
|
|
||||||
} else {
|
|
||||||
(AssetId::default(), false, false)
|
|
||||||
};
|
|
||||||
|
|
||||||
extracted_uinodes.uinodes.insert(
|
|
||||||
entity,
|
|
||||||
ExtractedUiNode {
|
|
||||||
stack_index,
|
|
||||||
transform: transform.compute_matrix(),
|
|
||||||
color: color.0,
|
|
||||||
rect: Rect {
|
|
||||||
min: Vec2::ZERO,
|
|
||||||
max: uinode.calculated_size,
|
|
||||||
},
|
|
||||||
clip: clip.map(|clip| clip.clip),
|
|
||||||
image,
|
|
||||||
atlas_size: None,
|
|
||||||
flip_x,
|
|
||||||
flip_y,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extracted_uinodes.uinodes.insert(
|
||||||
|
entity,
|
||||||
|
ExtractedUiNode {
|
||||||
|
stack_index: uinode.stack_index,
|
||||||
|
transform: transform.compute_matrix(),
|
||||||
|
color: color.0,
|
||||||
|
rect: Rect {
|
||||||
|
min: Vec2::ZERO,
|
||||||
|
max: uinode.calculated_size,
|
||||||
|
},
|
||||||
|
clip: clip.map(|clip| clip.clip),
|
||||||
|
image,
|
||||||
|
atlas_size: None,
|
||||||
|
flip_x,
|
||||||
|
flip_y,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,7 +603,6 @@ pub fn extract_text_uinodes(
|
|||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||||
ui_stack: Extract<Res<UiStack>>,
|
|
||||||
ui_scale: Extract<Res<UiScale>>,
|
ui_scale: Extract<Res<UiScale>>,
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
@ -647,51 +624,49 @@ pub fn extract_text_uinodes(
|
|||||||
|
|
||||||
let inverse_scale_factor = (scale_factor as f32).recip();
|
let inverse_scale_factor = (scale_factor as f32).recip();
|
||||||
|
|
||||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
for (uinode, global_transform, text, text_layout_info, view_visibility, clip) in
|
||||||
if let Ok((uinode, global_transform, text, text_layout_info, view_visibility, clip)) =
|
uinode_query.iter()
|
||||||
uinode_query.get(*entity)
|
{
|
||||||
|
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
|
||||||
|
if !view_visibility.get() || uinode.size().x == 0. || uinode.size().y == 0. {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let transform = global_transform.compute_matrix()
|
||||||
|
* Mat4::from_translation(-0.5 * uinode.size().extend(0.));
|
||||||
|
|
||||||
|
let mut color = Color::WHITE;
|
||||||
|
let mut current_section = usize::MAX;
|
||||||
|
for PositionedGlyph {
|
||||||
|
position,
|
||||||
|
atlas_info,
|
||||||
|
section_index,
|
||||||
|
..
|
||||||
|
} in &text_layout_info.glyphs
|
||||||
{
|
{
|
||||||
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
|
if *section_index != current_section {
|
||||||
if !view_visibility.get() || uinode.size().x == 0. || uinode.size().y == 0. {
|
color = text.sections[*section_index].style.color.as_rgba_linear();
|
||||||
continue;
|
current_section = *section_index;
|
||||||
}
|
}
|
||||||
let transform = global_transform.compute_matrix()
|
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
||||||
* Mat4::from_translation(-0.5 * uinode.size().extend(0.));
|
|
||||||
|
|
||||||
let mut color = Color::WHITE;
|
let mut rect = atlas.textures[atlas_info.glyph_index];
|
||||||
let mut current_section = usize::MAX;
|
rect.min *= inverse_scale_factor;
|
||||||
for PositionedGlyph {
|
rect.max *= inverse_scale_factor;
|
||||||
position,
|
extracted_uinodes.uinodes.insert(
|
||||||
atlas_info,
|
commands.spawn_empty().id(),
|
||||||
section_index,
|
ExtractedUiNode {
|
||||||
..
|
stack_index: uinode.stack_index,
|
||||||
} in &text_layout_info.glyphs
|
transform: transform
|
||||||
{
|
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
|
||||||
if *section_index != current_section {
|
color,
|
||||||
color = text.sections[*section_index].style.color.as_rgba_linear();
|
rect,
|
||||||
current_section = *section_index;
|
image: atlas.texture.id(),
|
||||||
}
|
atlas_size: Some(atlas.size * inverse_scale_factor),
|
||||||
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
clip: clip.map(|clip| clip.clip),
|
||||||
|
flip_x: false,
|
||||||
let mut rect = atlas.textures[atlas_info.glyph_index];
|
flip_y: false,
|
||||||
rect.min *= inverse_scale_factor;
|
},
|
||||||
rect.max *= inverse_scale_factor;
|
);
|
||||||
extracted_uinodes.uinodes.insert(
|
|
||||||
commands.spawn_empty().id(),
|
|
||||||
ExtractedUiNode {
|
|
||||||
stack_index,
|
|
||||||
transform: transform
|
|
||||||
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
|
|
||||||
color,
|
|
||||||
rect,
|
|
||||||
image: atlas.texture.id(),
|
|
||||||
atlas_size: Some(atlas.size * inverse_scale_factor),
|
|
||||||
clip: clip.map(|clip| clip.clip),
|
|
||||||
flip_x: false,
|
|
||||||
flip_y: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ pub fn ui_stack_system(
|
|||||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
||||||
zindex_query: Query<&ZIndex, With<Node>>,
|
zindex_query: Query<&ZIndex, With<Node>>,
|
||||||
children_query: Query<&Children>,
|
children_query: Query<&Children>,
|
||||||
|
mut update_query: Query<&mut Node>,
|
||||||
) {
|
) {
|
||||||
// Generate `StackingContext` tree
|
// Generate `StackingContext` tree
|
||||||
let mut global_context = StackingContext::default();
|
let mut global_context = StackingContext::default();
|
||||||
@ -55,6 +56,14 @@ pub fn ui_stack_system(
|
|||||||
ui_stack.uinodes.clear();
|
ui_stack.uinodes.clear();
|
||||||
ui_stack.uinodes.reserve(total_entry_count);
|
ui_stack.uinodes.reserve(total_entry_count);
|
||||||
fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context);
|
fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context);
|
||||||
|
|
||||||
|
for (i, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||||
|
update_query
|
||||||
|
.get_mut(*entity)
|
||||||
|
.unwrap()
|
||||||
|
.bypass_change_detection()
|
||||||
|
.stack_index = i as u32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate z-index based UI node tree
|
/// Generate z-index based UI node tree
|
||||||
|
@ -14,9 +14,12 @@ use thiserror::Error;
|
|||||||
#[derive(Component, Debug, Copy, Clone, Reflect)]
|
#[derive(Component, Debug, Copy, Clone, Reflect)]
|
||||||
#[reflect(Component, Default)]
|
#[reflect(Component, Default)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
/// The size of the node as width and height in logical pixels.
|
/// The order of the node in the UI layout.
|
||||||
|
/// Nodes with a higher stack index are drawn on top of and recieve interactions before nodes with lower stack indices.
|
||||||
|
pub(crate) stack_index: u32,
|
||||||
|
/// The size of the node as width and height in logical pixels
|
||||||
///
|
///
|
||||||
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
/// automatically calculated by [`super::layout::ui_layout_system`]
|
||||||
pub(crate) calculated_size: Vec2,
|
pub(crate) calculated_size: Vec2,
|
||||||
/// The width of this node's outline.
|
/// The width of this node's outline.
|
||||||
/// If this value is `Auto`, negative or `0.` then no outline will be rendered.
|
/// If this value is `Auto`, negative or `0.` then no outline will be rendered.
|
||||||
@ -39,6 +42,12 @@ impl Node {
|
|||||||
self.calculated_size
|
self.calculated_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The order of the node in the UI layout.
|
||||||
|
/// Nodes with a higher stack index are drawn on top of and recieve interactions before nodes with lower stack indices.
|
||||||
|
pub const fn stack_index(&self) -> u32 {
|
||||||
|
self.stack_index
|
||||||
|
}
|
||||||
|
|
||||||
/// The calculated node size as width and height in logical pixels before rounding.
|
/// The calculated node size as width and height in logical pixels before rounding.
|
||||||
///
|
///
|
||||||
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
/// Automatically calculated by [`super::layout::ui_layout_system`].
|
||||||
@ -92,6 +101,7 @@ impl Node {
|
|||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
pub const DEFAULT: Self = Self {
|
pub const DEFAULT: Self = Self {
|
||||||
|
stack_index: 0,
|
||||||
calculated_size: Vec2::ZERO,
|
calculated_size: Vec2::ZERO,
|
||||||
outline_width: 0.,
|
outline_width: 0.,
|
||||||
outline_offset: 0.,
|
outline_offset: 0.,
|
||||||
|
Loading…
Reference in New Issue
Block a user