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:
ickshonpe 2023-10-31 23:32:51 +00:00 committed by GitHub
parent 44928e0df4
commit 563d6e36bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 291 additions and 297 deletions

View File

@ -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,
},
);
}
} }
} }
} }

View File

@ -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

View File

@ -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.,