From bf32dbed41376a47e574340819a10bfb5ed7251e Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 11:26:37 -0400 Subject: [PATCH 01/27] Add ZIndex component and consistent UI stack --- Cargo.toml | 10 ++ crates/bevy_ui/src/focus.rs | 76 ++++++----- crates/bevy_ui/src/lib.rs | 12 +- crates/bevy_ui/src/render/mod.rs | 149 +++++++++++---------- crates/bevy_ui/src/stack.rs | 104 ++++++++++++++ crates/bevy_ui/src/ui_node.rs | 21 +++ crates/bevy_ui/src/update.rs | 163 +--------------------- examples/ui/z_index.rs | 223 +++++++++++++++++++++++++++++++ 8 files changed, 487 insertions(+), 271 deletions(-) create mode 100644 crates/bevy_ui/src/stack.rs create mode 100644 examples/ui/z_index.rs diff --git a/Cargo.toml b/Cargo.toml index f2157f120d846..2764cae341712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1442,6 +1442,16 @@ description = "Demonstrates transparency for UI" category = "UI (User Interface)" wasm = true +[[example]] +name = "z_index" +path = "examples/ui/z_index.rs" + +[package.metadata.example.z_index] +name = "UI Z-INDEX" +description = "Demonstrates z-index for UI" +category = "UI (User Interface)" +wasm = true + [[example]] name = "ui" path = "examples/ui/ui.rs" diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index f83054b2a8a14..9139defdd5ef1 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -1,4 +1,4 @@ -use crate::{entity::UiCameraConfig, CalculatedClip, Node}; +use crate::{entity::UiCameraConfig, CalculatedClip, Node, UiStack}; use bevy_ecs::{ entity::Entity, prelude::Component, @@ -11,7 +11,6 @@ use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::camera::{Camera, RenderTarget}; use bevy_render::view::ComputedVisibility; use bevy_transform::components::GlobalTransform; -use bevy_utils::FloatOrd; use bevy_window::Windows; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -71,6 +70,7 @@ pub fn ui_focus_system( windows: Res, mouse_button_input: Res>, touches_input: Res, + ui_stack: Res, mut node_query: Query<( Entity, &Node, @@ -123,10 +123,14 @@ pub fn ui_focus_system( .find_map(|window| window.cursor_position()) .or_else(|| touches_input.first_pressed_position()); - let mut moused_over_z_sorted_nodes = node_query - .iter_mut() - .filter_map( - |(entity, node, global_transform, interaction, focus_policy, clip, visibility)| { + let moused_over_nodes = ui_stack + .uinodes + .iter() + .rev() + .filter_map(|entity| { + if let Ok((entity, node, global_transform, interaction, _, clip, visibility)) = + node_query.get_mut(*entity) + { // Nodes that are not rendered should not be interactable if let Some(computed_visibility) = visibility { if !computed_visibility.is_visible() { @@ -161,7 +165,7 @@ pub fn ui_focus_system( }; if contains_cursor { - Some((entity, focus_policy, interaction, FloatOrd(position.z))) + return Some(entity); } else { if let Some(mut interaction) = interaction { if *interaction == Interaction::Hovered @@ -170,46 +174,48 @@ pub fn ui_focus_system( *interaction = Interaction::None; } } - None + return None; } - }, - ) + } + None + }) .collect::>(); - moused_over_z_sorted_nodes.sort_by_key(|(_, _, _, z)| -*z); - - let mut moused_over_z_sorted_nodes = moused_over_z_sorted_nodes.into_iter(); // set Clicked or Hovered on top nodes - for (entity, focus_policy, interaction, _) in moused_over_z_sorted_nodes.by_ref() { - if let Some(mut interaction) = interaction { - if mouse_clicked { - // only consider nodes with Interaction "clickable" - if *interaction != Interaction::Clicked { - *interaction = Interaction::Clicked; - // if the mouse was simultaneously released, reset this Interaction in the next - // frame - if mouse_released { - state.entities_to_reset.push(entity); + for entity in &moused_over_nodes { + if let Ok((_, _, _, interaction, focus_policy, _, _)) = node_query.get_mut(*entity) { + if let Some(mut interaction) = interaction { + if mouse_clicked { + // only consider nodes with Interaction "clickable" + if *interaction != Interaction::Clicked { + *interaction = Interaction::Clicked; + // if the mouse was simultaneously released, reset this Interaction in the next + // frame + if mouse_released { + state.entities_to_reset.push(*entity); + } } + } else if *interaction == Interaction::None { + *interaction = Interaction::Hovered; } - } else if *interaction == Interaction::None { - *interaction = Interaction::Hovered; } - } - match focus_policy.cloned().unwrap_or(FocusPolicy::Block) { - FocusPolicy::Block => { - break; + match focus_policy.cloned().unwrap_or(FocusPolicy::Block) { + FocusPolicy::Block => { + break; + } + FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ } } - FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ } } } // reset lower nodes to None - for (_entity, _focus_policy, interaction, _) in moused_over_z_sorted_nodes { - if let Some(mut interaction) = interaction { - // don't reset clicked nodes because they're handled separately - if *interaction != Interaction::Clicked && *interaction != Interaction::None { - *interaction = Interaction::None; + for entity in &moused_over_nodes { + if let Ok((_, _, _, interaction, _, _, _)) = node_query.get_mut(*entity) { + if let Some(mut interaction) = interaction { + // don't reset clicked nodes because they're handled separately + if *interaction != Interaction::Clicked && *interaction != Interaction::None { + *interaction = Interaction::None; + } } } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index e5a2b21c702c9..d93a9ff33b3bc 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -6,6 +6,7 @@ mod flex; mod focus; mod geometry; mod render; +mod stack; mod ui_node; pub mod entity; @@ -33,7 +34,9 @@ use bevy_ecs::{ use bevy_input::InputSystem; use bevy_transform::TransformSystem; use bevy_window::ModifiesWindows; -use update::{ui_z_system, update_clipping_system}; +use stack::ui_stack_system; +pub use stack::UiStack; +use update::update_clipping_system; use crate::prelude::UiCameraConfig; @@ -48,6 +51,8 @@ pub enum UiSystem { Flex, /// After this label, input interactions with UI entities have been updated for this frame Focus, + /// After this label, the [`UiStack`] resource has been updated + Stack, } /// The current scale of the UI. @@ -71,6 +76,7 @@ impl Plugin for UiPlugin { app.add_plugin(ExtractComponentPlugin::::default()) .init_resource::() .init_resource::() + .init_resource::() .register_type::() .register_type::() .register_type::() @@ -119,9 +125,7 @@ impl Plugin for UiPlugin { ) .add_system_to_stage( CoreStage::PostUpdate, - ui_z_system - .after(UiSystem::Flex) - .before(TransformSystem::TransformPropagate), + ui_stack_system.label(UiSystem::Stack), ) .add_system_to_stage( CoreStage::PostUpdate, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 6bc0abee94ec1..4ad56c66385c2 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -5,7 +5,7 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; pub use pipeline::*; pub use render_pass::*; -use crate::{prelude::UiCameraConfig, CalculatedClip, Node, UiColor, UiImage}; +use crate::{prelude::UiCameraConfig, CalculatedClip, Node, UiColor, UiImage, UiStack}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; @@ -160,6 +160,7 @@ fn get_ui_graph(render_app: &mut App) -> RenderGraph { } pub struct ExtractedUiNode { + pub stack_index: usize, pub transform: Mat4, pub color: Color, pub rect: Rect, @@ -176,6 +177,7 @@ pub struct ExtractedUiNodes { pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, + ui_stack: Extract>, uinode_query: Extract< Query<( &Node, @@ -188,30 +190,33 @@ pub fn extract_uinodes( >, ) { extracted_uinodes.uinodes.clear(); - for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() { - if !visibility.is_visible() { - continue; - } - let image = image.0.clone_weak(); - // Skip loading images - if !images.contains(&image) { - continue; - } - // Skip completely transparent nodes - if color.0.a() == 0.0 { - continue; + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((uinode, transform, color, image, visibility, clip)) = uinode_query.get(*entity) { + if !visibility.is_visible() { + continue; + } + let image = image.0.clone_weak(); + // Skip loading images + if !images.contains(&image) { + continue; + } + // Skip completely transparent nodes + if color.0.a() == 0.0 { + continue; + } + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: transform.compute_matrix(), + color: color.0, + rect: bevy_sprite::Rect { + min: Vec2::ZERO, + max: uinode.size, + }, + image, + atlas_size: None, + clip: clip.map(|clip| clip.clip), + }); } - extracted_uinodes.uinodes.push(ExtractedUiNode { - transform: transform.compute_matrix(), - color: color.0, - rect: bevy_sprite::Rect { - min: Vec2::ZERO, - max: uinode.size, - }, - image, - atlas_size: None, - clip: clip.map(|clip| clip.clip), - }); } } @@ -274,6 +279,7 @@ pub fn extract_text_uinodes( texture_atlases: Extract>>, text_pipeline: Extract>, windows: Extract>, + ui_stack: Extract>, uinode_query: Extract< Query<( Entity, @@ -286,51 +292,54 @@ pub fn extract_text_uinodes( >, ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, uinode, global_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 { - continue; - } - if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { - let text_glyphs = &text_layout.glyphs; - let alignment_offset = (uinode.size / -2.0).extend(0.0); - - let mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((entity, uinode, global_transform, text, visibility, clip)) = uinode_query.get(*entity) { + 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 { + continue; + } + if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { + let text_glyphs = &text_layout.glyphs; + let alignment_offset = (uinode.size / -2.0).extend(0.0); + + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; + } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let texture = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = atlas.textures[index]; + let atlas_size = Some(atlas.size); + + // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` + let extracted_transform = global_transform.compute_matrix() + * Mat4::from_scale(Vec3::splat(scale_factor.recip())) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: extracted_transform, + color, + rect, + image: texture, + atlas_size, + clip: clip.map(|clip| clip.clip), + }); } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let texture = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - let rect = atlas.textures[index]; - let atlas_size = Some(atlas.size); - - // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` - let extracted_transform = global_transform.compute_matrix() - * Mat4::from_scale(Vec3::splat(scale_factor.recip())) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); - - extracted_uinodes.uinodes.push(ExtractedUiNode { - transform: extracted_transform, - color, - rect, - image: texture, - atlas_size, - clip: clip.map(|clip| clip.clip), - }); } } } @@ -384,10 +393,10 @@ pub fn prepare_uinodes( ) { ui_meta.vertices.clear(); - // sort by increasing z for correct transparency + // sort by ui stack index, starting from the deepest node extracted_uinodes .uinodes - .sort_by(|a, b| FloatOrd(a.transform.w_axis[2]).cmp(&FloatOrd(b.transform.w_axis[2]))); + .sort_by_key(|node| node.stack_index); let mut start = 0; let mut end = 0; diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs new file mode 100644 index 0000000000000..32dbca4790293 --- /dev/null +++ b/crates/bevy_ui/src/stack.rs @@ -0,0 +1,104 @@ +//! This module contains the systems that update the stored UI nodes stack + +use bevy_ecs::prelude::*; +use bevy_hierarchy::prelude::*; + +use crate::{ZIndex, Node}; + +/// The current UI stack, which contains all UI nodes ordered by their depth. +/// +/// The first entry is the furthest node from the camera and is the first one to get rendered +/// while the last entry is the first node to receive interactions. +#[derive(Debug, Resource, Default)] +pub struct UiStack { + pub uinodes: Vec, +} + +#[derive(Default)] +struct StackingContext { + pub entries: Vec, +} + +struct StackingContextEntry { + pub z_index: i32, + pub entity: Entity, + pub stack: StackingContext, +} + +/// Generates the render stack for UI nodes. +pub fn ui_stack_system( + mut ui_stack: ResMut, + root_node_query: Query, Without)>, + zindex_query: Query<&ZIndex, With>, + children_query: Query<&Children>, +) { + let mut global_context = StackingContext::default(); + + let mut total_entry_count: usize = 0; + for entity in &root_node_query { + insert_context_hierarchy( + &zindex_query, + &children_query, + entity, + &mut global_context, + None, + &mut total_entry_count, + ); + } + + *ui_stack = UiStack { + uinodes: Vec::::with_capacity(total_entry_count), + }; + + fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context); +} + +fn insert_context_hierarchy( + zindex_query: &Query<&ZIndex, With>, + children_query: &Query<&Children>, + entity: Entity, + global_context: &mut StackingContext, + parent_context: Option<&mut StackingContext>, + total_entry_count: &mut usize, +) { + let mut new_context = StackingContext::default(); + if let Ok(children) = children_query.get(entity) { + // reserve space for all children. in practice, some may not get pushed. + new_context.entries.reserve_exact(children.len()); + + for entity in children { + insert_context_hierarchy( + zindex_query, + children_query, + *entity, + global_context, + Some(&mut new_context), + total_entry_count, + ); + } + } + + let z_index = zindex_query.get(entity).unwrap_or(&ZIndex::Local(0)); + let (entity_context, z_index) = match z_index { + ZIndex::Local(value) => (parent_context.unwrap_or(global_context), *value), + ZIndex::Global(value) => (global_context, *value), + }; + + *total_entry_count += 1; + entity_context.entries.push(StackingContextEntry { + z_index, + entity, + stack: new_context, + }); +} + +fn fill_stack_recursively(result: &mut Vec, stack: &mut StackingContext) { + // sort entries by ascending z_index, while ensuring that siblings + // with the same local z_index will keep their ordering. + stack.entries.sort_by_key(|e| e.z_index); + + for entry in &mut stack.entries { + result.push(entry.entity); + fill_stack_recursively(result, &mut entry.stack); + } +} diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index e741550db06e2..89443b9cc9548 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -410,3 +410,24 @@ pub struct CalculatedClip { /// The rect of the clip pub clip: bevy_sprite::Rect, } + +/// Indicates that this node has special requirements for the order of depth in which +/// it should appear in the UI instead of relying solely on the hierarchy of the UI. +/// +/// Omitting this component on a node will yield the same result as using [`ZIndex::Local(0)`]. +#[derive(Component, Copy, Clone, Debug, Reflect)] +pub enum ZIndex { + /// Indicates the order in which this node should be rendered relative to its siblings. + /// + /// For root UI nodes (nodes that have to parent), using this and [`ZIndex::Global`] is equivalent. + Local(i32), + /// Indicates the order in which this node should be rendered relative to all other nodes + /// that also use have a [`ZIndex::Global`] value. + Global(i32), +} + +impl Default for ZIndex { + fn default() -> Self { + Self::Local(0) + } +} diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index b0cf91aa6abb3..6b1a1acb20149 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -11,58 +11,7 @@ use bevy_ecs::{ use bevy_hierarchy::{Children, Parent}; use bevy_math::Vec2; use bevy_sprite::Rect; -use bevy_transform::components::{GlobalTransform, Transform}; - -/// The resolution of `Z` values for UI -pub const UI_Z_STEP: f32 = 0.001; - -/// Updates transforms of nodes to fit with the `Z` system -pub fn ui_z_system( - root_node_query: Query, Without)>, - mut node_query: Query<&mut Transform, With>, - children_query: Query<&Children>, -) { - let mut current_global_z = 0.0; - for entity in &root_node_query { - current_global_z = update_hierarchy( - &children_query, - &mut node_query, - entity, - current_global_z, - current_global_z, - ); - } -} - -fn update_hierarchy( - children_query: &Query<&Children>, - node_query: &mut Query<&mut Transform, With>, - entity: Entity, - parent_global_z: f32, - mut current_global_z: f32, -) -> f32 { - current_global_z += UI_Z_STEP; - if let Ok(mut transform) = node_query.get_mut(entity) { - let new_z = current_global_z - parent_global_z; - // only trigger change detection when the new value is different - if transform.translation.z != new_z { - transform.translation.z = new_z; - } - } - if let Ok(children) = children_query.get(entity) { - let current_parent_global_z = current_global_z; - for child in children.iter().cloned() { - current_global_z = update_hierarchy( - children_query, - node_query, - child, - current_parent_global_z, - current_global_z, - ); - } - } - current_global_z -} +use bevy_transform::components::GlobalTransform; /// Updates clipping for all nodes pub fn update_clipping_system( @@ -130,113 +79,3 @@ fn update_clipping( } } } - -#[cfg(test)] -mod tests { - use bevy_ecs::{ - component::Component, - schedule::{Schedule, Stage, SystemStage}, - system::{CommandQueue, Commands}, - world::World, - }; - use bevy_hierarchy::BuildChildren; - use bevy_transform::components::Transform; - - use crate::Node; - - use super::{ui_z_system, UI_Z_STEP}; - - #[derive(Component, PartialEq, Debug, Clone)] - struct Label(&'static str); - - fn node_with_transform(name: &'static str) -> (Label, Node, Transform) { - (Label(name), Node::default(), Transform::IDENTITY) - } - - fn node_without_transform(name: &'static str) -> (Label, Node) { - (Label(name), Node::default()) - } - - fn get_steps(transform: &Transform) -> u32 { - (transform.translation.z / UI_Z_STEP).round() as u32 - } - - #[test] - fn test_ui_z_system() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - commands.spawn_bundle(node_with_transform("0")); - - commands - .spawn_bundle(node_with_transform("1")) - .with_children(|parent| { - parent - .spawn_bundle(node_with_transform("1-0")) - .with_children(|parent| { - parent.spawn_bundle(node_with_transform("1-0-0")); - parent.spawn_bundle(node_without_transform("1-0-1")); - parent.spawn_bundle(node_with_transform("1-0-2")); - }); - parent.spawn_bundle(node_with_transform("1-1")); - parent - .spawn_bundle(node_without_transform("1-2")) - .with_children(|parent| { - parent.spawn_bundle(node_with_transform("1-2-0")); - parent.spawn_bundle(node_with_transform("1-2-1")); - parent - .spawn_bundle(node_with_transform("1-2-2")) - .with_children(|_| ()); - parent.spawn_bundle(node_with_transform("1-2-3")); - }); - parent.spawn_bundle(node_with_transform("1-3")); - }); - - commands - .spawn_bundle(node_without_transform("2")) - .with_children(|parent| { - parent - .spawn_bundle(node_with_transform("2-0")) - .with_children(|_parent| ()); - parent - .spawn_bundle(node_with_transform("2-1")) - .with_children(|parent| { - parent.spawn_bundle(node_with_transform("2-1-0")); - }); - }); - queue.apply(&mut world); - - let mut schedule = Schedule::default(); - let mut update_stage = SystemStage::parallel(); - update_stage.add_system(ui_z_system); - schedule.add_stage("update", update_stage); - schedule.run(&mut world); - - let mut actual_result = world - .query::<(&Label, &Transform)>() - .iter(&world) - .map(|(name, transform)| (name.clone(), get_steps(transform))) - .collect::>(); - actual_result.sort_unstable_by_key(|(name, _)| name.0); - let expected_result = vec![ - (Label("0"), 1), - (Label("1"), 1), - (Label("1-0"), 1), - (Label("1-0-0"), 1), - // 1-0-1 has no transform - (Label("1-0-2"), 3), - (Label("1-1"), 5), - // 1-2 has no transform - (Label("1-2-0"), 1), - (Label("1-2-1"), 2), - (Label("1-2-2"), 3), - (Label("1-2-3"), 4), - (Label("1-3"), 11), - // 2 has no transform - (Label("2-0"), 1), - (Label("2-1"), 2), - (Label("2-1-0"), 1), - ]; - assert_eq!(actual_result, expected_result); - } -} diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs new file mode 100644 index 0000000000000..114b4957f279f --- /dev/null +++ b/examples/ui/z_index.rs @@ -0,0 +1,223 @@ +//! Demonstrates how to use z-index +//! Shows two colored buttons with transparent text. + +use bevy::prelude::*; + +fn main() { + App::new() + .insert_resource(ClearColor(Color::BLACK)) + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(grab) + .add_system(update_z_index_text) + .add_system_to_stage(CoreStage::PreUpdate, grabbed_move) + .run(); +} + +#[derive(Component)] +struct ZIndexText; + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn_bundle(Camera2dBundle::default()); + + let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf"); + + // prepare a stack node at the root that's above the stack container + spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::DARK_GREEN, + Some(ZIndex::Global(1)), + UiRect { + left: Val::Px(0.0), + bottom: Val::Px(0.0), + ..default() + }, + ); + + // prepare a stack node at the root that's under the stack container + spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::ORANGE, + Some(ZIndex::Global(-1)), + UiRect { + left: Val::Px(100.0), + bottom: Val::Px(100.0), + ..default() + }, + ); + + // prepare a stack of nodes that can be moved around inside their container. + let mut stacked_nodes = (0..9) + .map(|i| { + spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::rgb(0.1 + i as f32 * 0.1, 0.0, 0.0), + Some(ZIndex::Local(-4 + i)), + UiRect { + left: Val::Px(10.0 + (i as f32 * 47.5)), + bottom: Val::Px(10.0 + (i as f32 * 22.5)), + ..default() + }, + ) + }) + .collect::>(); + + // add a node that has no z-index + stacked_nodes.push(spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::PURPLE, + None, + UiRect { + left: Val::Px(10.0), + bottom: Val::Px(100.0), + ..default() + }, + )); + + // add a node with a global z-index + stacked_nodes.push(spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::PINK, + Some(ZIndex::Global(2)), + UiRect { + left: Val::Px(10.0), + bottom: Val::Px(150.0), + ..default() + }, + )); + + // spawn the stack container + commands + .spawn_bundle(NodeBundle { + color: Color::GRAY.into(), + style: Style { + size: Size::new(Val::Px(500.0), Val::Px(250.0)), + overflow: Overflow::Hidden, + margin: UiRect::all(Val::Auto), + ..default() + }, + ..default() + }) + .push_children(&stacked_nodes); +} + +fn spawn_stack_node( + commands: &mut Commands, + font_handle: Handle, + color: Color, + z_index: Option, + position: UiRect, +) -> Entity { + let text = commands + .spawn_bundle(TextBundle::from_section( + "", + TextStyle { + color: Color::WHITE, + font: font_handle, + font_size: 20.0, + }, + )) + .insert(ZIndexText) + .id(); + + let node = commands + .spawn_bundle(ButtonBundle { + color: color.into(), + style: Style { + position_type: PositionType::Absolute, + position, + size: Size::new(Val::Px(100.0), Val::Px(50.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + ..default() + }) + .insert(Grab) + .add_child(text) + .id(); + + if let Some(z_index) = z_index { + commands.entity(node).insert(z_index); + } + + node +} + +fn update_z_index_text( + mut text_query: Query<(&Parent, &mut Text), With>, + zindex_query: Query<&ZIndex>, +) { + for (parent, mut text) in &mut text_query { + let new_text = zindex_query + .get(parent.get()) + .map_or("No ZIndex".to_string(), |zindex| match zindex { + ZIndex::Local(value) => format!("Local({value})"), + ZIndex::Global(value) => format!("Global({value})"), + }); + + if text.sections[0].value != new_text { + text.sections[0].value = new_text; + } + } +} + +#[derive(Component, Copy, Clone, Debug)] +pub struct Grab; + +#[derive(Component, Copy, Clone, Debug)] +pub struct Grabbed { + pub cursor_position: Vec2, + pub cursor_offset: Vec2, +} + +fn grab( + mut commands: Commands, + query: Query<(Entity, &Interaction, Option<&Grabbed>), With>, + windows: Res, +) { + for (entity, interaction, grabbed) in query.iter() { + match interaction { + Interaction::Clicked => { + if grabbed.is_none() { + if let Some(cursor_position) = windows + .get_primary() + .and_then(|window| window.cursor_position()) + { + commands.entity(entity).insert(Grabbed { + cursor_position, + cursor_offset: Vec2::new(0.0, 0.0), + }); + } + } + } + _ => { + if grabbed.is_some() { + commands.entity(entity).remove::(); + } + } + }; + } +} + +fn grabbed_move(mut query: Query<(&mut Grabbed, &mut Style), With>, windows: Res) { + for (mut grabbed, mut style) in query.iter_mut() { + if let Some(cursor_position) = windows + .get_primary() + .and_then(|window| window.cursor_position()) + { + let offset = cursor_position - grabbed.cursor_position; + if grabbed.cursor_offset != offset { + style.position.left += offset.x - grabbed.cursor_offset.x; + style.position.bottom += offset.y - grabbed.cursor_offset.y; + + grabbed.cursor_offset = offset; + } + } + } +} From 1bd3fe1e6dee9c1569abebedbb91f888b6fcb3f2 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 12:29:12 -0400 Subject: [PATCH 02/27] Fix small issues after merging --- crates/bevy_ui/src/update.rs | 3 +-- examples/ui/z_index.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 5b8b8f8f32fe2..799dd579ef810 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -9,8 +9,7 @@ use bevy_ecs::{ system::{Commands, Query}, }; use bevy_hierarchy::{Children, Parent}; -use bevy_math::Vec2; -use bevy_sprite::Rect; +use bevy_math::Rect; use bevy_transform::components::GlobalTransform; /// Updates clipping for all nodes diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 114b4957f279f..2923e6f212203 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -29,8 +29,8 @@ fn setup(mut commands: Commands, asset_server: Res) { Color::DARK_GREEN, Some(ZIndex::Global(1)), UiRect { - left: Val::Px(0.0), - bottom: Val::Px(0.0), + left: Val::Px(20.0), + bottom: Val::Px(80.0), ..default() }, ); @@ -42,8 +42,8 @@ fn setup(mut commands: Commands, asset_server: Res) { Color::ORANGE, Some(ZIndex::Global(-1)), UiRect { - left: Val::Px(100.0), - bottom: Val::Px(100.0), + left: Val::Px(20.0), + bottom: Val::Px(20.0), ..default() }, ); @@ -73,7 +73,7 @@ fn setup(mut commands: Commands, asset_server: Res) { None, UiRect { left: Val::Px(10.0), - bottom: Val::Px(100.0), + bottom: Val::Px(120.0), ..default() }, )); @@ -86,7 +86,7 @@ fn setup(mut commands: Commands, asset_server: Res) { Some(ZIndex::Global(2)), UiRect { left: Val::Px(10.0), - bottom: Val::Px(150.0), + bottom: Val::Px(180.0), ..default() }, )); From 04e5d7a782ae5492be39fe326e2725ff32a34cbc Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 12:40:07 -0400 Subject: [PATCH 03/27] Run fmt and clippy --- crates/bevy_ui/src/render/mod.rs | 4 +++- crates/bevy_ui/src/stack.rs | 6 +++--- crates/bevy_ui/src/ui_node.rs | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 468e810ed215c..87a078380f549 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -293,7 +293,9 @@ pub fn extract_text_uinodes( ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((entity, uinode, global_transform, text, visibility, clip)) = uinode_query.get(*entity) { + if let Ok((entity, uinode, global_transform, text, visibility, clip)) = + uinode_query.get(*entity) + { if !visibility.is_visible() { continue; } diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 32dbca4790293..1a457d0abd816 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -3,10 +3,10 @@ use bevy_ecs::prelude::*; use bevy_hierarchy::prelude::*; -use crate::{ZIndex, Node}; +use crate::{Node, ZIndex}; /// The current UI stack, which contains all UI nodes ordered by their depth. -/// +/// /// The first entry is the furthest node from the camera and is the first one to get rendered /// while the last entry is the first node to receive interactions. #[derive(Debug, Resource, Default)] @@ -49,7 +49,7 @@ pub fn ui_stack_system( *ui_stack = UiStack { uinodes: Vec::::with_capacity(total_entry_count), }; - + fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context); } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 5171e2cb0f2db..191135a82d8c2 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -413,12 +413,12 @@ pub struct CalculatedClip { /// Indicates that this node has special requirements for the order of depth in which /// it should appear in the UI instead of relying solely on the hierarchy of the UI. -/// +/// /// Omitting this component on a node will yield the same result as using [`ZIndex::Local(0)`]. #[derive(Component, Copy, Clone, Debug, Reflect)] pub enum ZIndex { /// Indicates the order in which this node should be rendered relative to its siblings. - /// + /// /// For root UI nodes (nodes that have to parent), using this and [`ZIndex::Global`] is equivalent. Local(i32), /// Indicates the order in which this node should be rendered relative to all other nodes From 321de0914ea997c34b7e38b5cde479c3cf143cae Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 12:50:02 -0400 Subject: [PATCH 04/27] Fix clippy errors --- crates/bevy_ui/src/focus.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 9139defdd5ef1..511aed3078deb 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -166,16 +166,14 @@ pub fn ui_focus_system( if contains_cursor { return Some(entity); - } else { - if let Some(mut interaction) = interaction { - if *interaction == Interaction::Hovered - || (cursor_position.is_none() && *interaction != Interaction::None) - { - *interaction = Interaction::None; - } + } else if let Some(mut interaction) = interaction { + if *interaction == Interaction::Hovered + || (cursor_position.is_none() && *interaction != Interaction::None) + { + *interaction = Interaction::None; } - return None; } + return None; } None }) @@ -210,12 +208,10 @@ pub fn ui_focus_system( } // reset lower nodes to None for entity in &moused_over_nodes { - if let Ok((_, _, _, interaction, _, _, _)) = node_query.get_mut(*entity) { - if let Some(mut interaction) = interaction { - // don't reset clicked nodes because they're handled separately - if *interaction != Interaction::Clicked && *interaction != Interaction::None { - *interaction = Interaction::None; - } + if let Ok((_, _, _, Some(mut interaction), _, _, _)) = node_query.get_mut(*entity) { + // don't reset clicked nodes because they're handled separately + if *interaction != Interaction::Clicked && *interaction != Interaction::None { + *interaction = Interaction::None; } } } From 12cc199b637e1e5a77c160d70d58f59ed38f2e9b Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 13:12:19 -0400 Subject: [PATCH 05/27] Fix example error --- examples/README.md | 1 + examples/ui/z_index.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 8f8b57759727a..281b3f450cf20 100644 --- a/examples/README.md +++ b/examples/README.md @@ -314,6 +314,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/scaling.rs) | Illustrates how to scale the UI +[UI Z-INDEX](../examples/ui/z_index.rs) | Demonstrates z-index for UI ## Window diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 2923e6f212203..3a6674da2860d 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -81,7 +81,7 @@ fn setup(mut commands: Commands, asset_server: Res) { // add a node with a global z-index stacked_nodes.push(spawn_stack_node( &mut commands, - font_handle.clone(), + font_handle, Color::PINK, Some(ZIndex::Global(2)), UiRect { From 0c1bbabad999ab5a25b506ce89aa3235f0c4fa22 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Tue, 6 Sep 2022 23:56:18 -0400 Subject: [PATCH 06/27] Simplify the z_index example --- examples/ui/z_index.rs | 295 ++++++++++++++--------------------------- 1 file changed, 102 insertions(+), 193 deletions(-) diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 3a6674da2860d..5a3e64d28b8a3 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -1,5 +1,7 @@ //! Demonstrates how to use z-index -//! Shows two colored buttons with transparent text. +//! +//! It uses colored boxes with different z-index values to demonstrate how it can affect the order of +//! depth of nodes compared to their siblings, but also compared to the entire UI. use bevy::prelude::*; @@ -8,216 +10,123 @@ fn main() { .insert_resource(ClearColor(Color::BLACK)) .add_plugins(DefaultPlugins) .add_startup_system(setup) - .add_system(grab) - .add_system(update_z_index_text) - .add_system_to_stage(CoreStage::PreUpdate, grabbed_move) .run(); } #[derive(Component)] struct ZIndexText; -fn setup(mut commands: Commands, asset_server: Res) { +fn setup(mut commands: Commands) { commands.spawn_bundle(Camera2dBundle::default()); - let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf"); - - // prepare a stack node at the root that's above the stack container - spawn_stack_node( - &mut commands, - font_handle.clone(), - Color::DARK_GREEN, - Some(ZIndex::Global(1)), - UiRect { - left: Val::Px(20.0), - bottom: Val::Px(80.0), - ..default() - }, - ); - - // prepare a stack node at the root that's under the stack container - spawn_stack_node( - &mut commands, - font_handle.clone(), - Color::ORANGE, - Some(ZIndex::Global(-1)), - UiRect { - left: Val::Px(20.0), - bottom: Val::Px(20.0), - ..default() - }, - ); - - // prepare a stack of nodes that can be moved around inside their container. - let mut stacked_nodes = (0..9) - .map(|i| { - spawn_stack_node( - &mut commands, - font_handle.clone(), - Color::rgb(0.1 + i as f32 * 0.1, 0.0, 0.0), - Some(ZIndex::Local(-4 + i)), - UiRect { - left: Val::Px(10.0 + (i as f32 * 47.5)), - bottom: Val::Px(10.0 + (i as f32 * 22.5)), - ..default() - }, - ) - }) - .collect::>(); - - // add a node that has no z-index - stacked_nodes.push(spawn_stack_node( - &mut commands, - font_handle.clone(), - Color::PURPLE, - None, - UiRect { - left: Val::Px(10.0), - bottom: Val::Px(120.0), - ..default() - }, - )); - - // add a node with a global z-index - stacked_nodes.push(spawn_stack_node( - &mut commands, - font_handle, - Color::PINK, - Some(ZIndex::Global(2)), - UiRect { - left: Val::Px(10.0), - bottom: Val::Px(180.0), - ..default() - }, - )); - - // spawn the stack container + // spawn the container with default z-index. + // ommiting the z-index component is equivalent to inserting `ZIndex::Local(0)`. + // because this is a root UI node, it is also equivalent to inserting `ZIndex::Global(0)`. commands .spawn_bundle(NodeBundle { color: Color::GRAY.into(), style: Style { - size: Size::new(Val::Px(500.0), Val::Px(250.0)), - overflow: Overflow::Hidden, + size: Size::new(Val::Px(180.0), Val::Px(100.0)), margin: UiRect::all(Val::Auto), ..default() }, ..default() }) - .push_children(&stacked_nodes); -} - -fn spawn_stack_node( - commands: &mut Commands, - font_handle: Handle, - color: Color, - z_index: Option, - position: UiRect, -) -> Entity { - let text = commands - .spawn_bundle(TextBundle::from_section( - "", - TextStyle { - color: Color::WHITE, - font: font_handle, - font_size: 20.0, - }, - )) - .insert(ZIndexText) - .id(); - - let node = commands - .spawn_bundle(ButtonBundle { - color: color.into(), - style: Style { - position_type: PositionType::Absolute, - position, - size: Size::new(Val::Px(100.0), Val::Px(50.0)), - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, + .with_children(|parent| { + // spawn a node with no z-index. + // this has the same result as using `ZIndex::Local(0)`. + // this uses the default depth ordering which is based on where this node is in the hierarchy. + parent.spawn_bundle(ButtonBundle { + color: Color::RED.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(10.0), + bottom: Val::Px(40.0), + ..default() + }, + size: Size::new(Val::Px(100.0), Val::Px(50.0)), + ..default() + }, ..default() - }, - ..default() - }) - .insert(Grab) - .add_child(text) - .id(); - - if let Some(z_index) = z_index { - commands.entity(node).insert(z_index); - } - - node -} - -fn update_z_index_text( - mut text_query: Query<(&Parent, &mut Text), With>, - zindex_query: Query<&ZIndex>, -) { - for (parent, mut text) in &mut text_query { - let new_text = zindex_query - .get(parent.get()) - .map_or("No ZIndex".to_string(), |zindex| match zindex { - ZIndex::Local(value) => format!("Local({value})"), - ZIndex::Global(value) => format!("Global({value})"), }); - if text.sections[0].value != new_text { - text.sections[0].value = new_text; - } - } -} - -#[derive(Component, Copy, Clone, Debug)] -pub struct Grab; - -#[derive(Component, Copy, Clone, Debug)] -pub struct Grabbed { - pub cursor_position: Vec2, - pub cursor_offset: Vec2, -} - -fn grab( - mut commands: Commands, - query: Query<(Entity, &Interaction, Option<&Grabbed>), With>, - windows: Res, -) { - for (entity, interaction, grabbed) in query.iter() { - match interaction { - Interaction::Clicked => { - if grabbed.is_none() { - if let Some(cursor_position) = windows - .get_primary() - .and_then(|window| window.cursor_position()) - { - commands.entity(entity).insert(Grabbed { - cursor_position, - cursor_offset: Vec2::new(0.0, 0.0), - }); - } - } - } - _ => { - if grabbed.is_some() { - commands.entity(entity).remove::(); - } - } - }; - } -} - -fn grabbed_move(mut query: Query<(&mut Grabbed, &mut Style), With>, windows: Res) { - for (mut grabbed, mut style) in query.iter_mut() { - if let Some(cursor_position) = windows - .get_primary() - .and_then(|window| window.cursor_position()) - { - let offset = cursor_position - grabbed.cursor_position; - if grabbed.cursor_offset != offset { - style.position.left += offset.x - grabbed.cursor_offset.x; - style.position.bottom += offset.y - grabbed.cursor_offset.y; - - grabbed.cursor_offset = offset; - } - } - } + // spawn a node with a positive local z-index of 2. + // it will show above other nodes in the grey container. + parent + .spawn_bundle(ButtonBundle { + color: Color::BLUE.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(45.0), + bottom: Val::Px(30.0), + ..default() + }, + size: Size::new(Val::Px(100.0), Val::Px(50.0)), + ..default() + }, + ..default() + }) + .insert(ZIndex::Local(2)); + + // spawn a node with a negative local z-index. + // it will show under all other nodes in the grey container. + parent + .spawn_bundle(ButtonBundle { + color: Color::GREEN.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(70.0), + bottom: Val::Px(20.0), + ..default() + }, + size: Size::new(Val::Px(100.0), Val::Px(75.0)), + ..default() + }, + ..default() + }) + .insert(ZIndex::Local(-1)); + + // spawn a node with a positive global z-index of 1. + // it will show above all other nodes, because it's the highest global z-index in this example app. + // by default, boxes all share the global z-index of 0 that the grey container is implicitly added to. + parent + .spawn_bundle(ButtonBundle { + color: Color::PINK.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(15.0), + bottom: Val::Px(-5.0), + ..default() + }, + size: Size::new(Val::Px(100.0), Val::Px(60.0)), + ..default() + }, + ..default() + }) + .insert(ZIndex::Global(1)); + + // spawn a node with a negative global z-index. + // this will show under all other nodes, including its parent, because -1 is the lowest global z-index + // in this example app. + parent + .spawn_bundle(ButtonBundle { + color: Color::YELLOW.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(-10.0), + bottom: Val::Px(-10.0), + ..default() + }, + size: Size::new(Val::Px(100.0), Val::Px(50.0)), + ..default() + }, + ..default() + }) + .insert(ZIndex::Global(-1)); + }); } From 2d2d3352e25b54da903af8734f034416dcc1bfd4 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Wed, 7 Sep 2022 00:04:38 -0400 Subject: [PATCH 07/27] Replace one color in z-index example --- examples/ui/z_index.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 5a3e64d28b8a3..49159b51a2d2e 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -36,7 +36,7 @@ fn setup(mut commands: Commands) { // spawn a node with no z-index. // this has the same result as using `ZIndex::Local(0)`. // this uses the default depth ordering which is based on where this node is in the hierarchy. - parent.spawn_bundle(ButtonBundle { + parent.spawn_bundle(NodeBundle { color: Color::RED.into(), style: Style { position_type: PositionType::Absolute, @@ -54,7 +54,7 @@ fn setup(mut commands: Commands) { // spawn a node with a positive local z-index of 2. // it will show above other nodes in the grey container. parent - .spawn_bundle(ButtonBundle { + .spawn_bundle(NodeBundle { color: Color::BLUE.into(), style: Style { position_type: PositionType::Absolute, @@ -73,7 +73,7 @@ fn setup(mut commands: Commands) { // spawn a node with a negative local z-index. // it will show under all other nodes in the grey container. parent - .spawn_bundle(ButtonBundle { + .spawn_bundle(NodeBundle { color: Color::GREEN.into(), style: Style { position_type: PositionType::Absolute, @@ -93,8 +93,8 @@ fn setup(mut commands: Commands) { // it will show above all other nodes, because it's the highest global z-index in this example app. // by default, boxes all share the global z-index of 0 that the grey container is implicitly added to. parent - .spawn_bundle(ButtonBundle { - color: Color::PINK.into(), + .spawn_bundle(NodeBundle { + color: Color::PURPLE.into(), style: Style { position_type: PositionType::Absolute, position: UiRect { @@ -113,7 +113,7 @@ fn setup(mut commands: Commands) { // this will show under all other nodes, including its parent, because -1 is the lowest global z-index // in this example app. parent - .spawn_bundle(ButtonBundle { + .spawn_bundle(NodeBundle { color: Color::YELLOW.into(), style: Style { position_type: PositionType::Absolute, From 97205c7b7bfc3c689c387b6087098434b0f5e8a5 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Wed, 7 Sep 2022 22:04:37 -0400 Subject: [PATCH 08/27] Add ZIndex to UI bundles and change example to reflect that --- crates/bevy_ui/src/entity.rs | 12 +++- examples/ui/z_index.rs | 120 +++++++++++++++++------------------ 2 files changed, 68 insertions(+), 64 deletions(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 3aa587f564d0d..ca8b32c20cb49 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,7 +2,7 @@ use crate::{ widget::{Button, ImageMode}, - CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, + CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, ZIndex, }; use bevy_ecs::{ bundle::Bundle, @@ -37,6 +37,8 @@ pub struct NodeBundle { 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, } /// A UI node that is an image @@ -64,6 +66,8 @@ pub struct ImageBundle { 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, } /// A UI node that is text @@ -87,6 +91,8 @@ pub struct TextBundle { 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, } impl TextBundle { @@ -135,6 +141,7 @@ impl Default for TextBundle { global_transform: Default::default(), visibility: Default::default(), computed_visibility: Default::default(), + z_index: Default::default(), } } } @@ -164,6 +171,8 @@ pub struct ButtonBundle { 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, } impl Default for ButtonBundle { @@ -180,6 +189,7 @@ impl Default for ButtonBundle { global_transform: Default::default(), visibility: Default::default(), computed_visibility: Default::default(), + z_index: Default::default(), } } } diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 49159b51a2d2e..02508a7493b63 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -20,8 +20,8 @@ fn setup(mut commands: Commands) { commands.spawn_bundle(Camera2dBundle::default()); // spawn the container with default z-index. - // ommiting the z-index component is equivalent to inserting `ZIndex::Local(0)`. - // because this is a root UI node, it is also equivalent to inserting `ZIndex::Global(0)`. + // the default z-index value is `ZIndex::Local(0)`. + // because this is a root UI node, using local or global values will do the same thing. commands .spawn_bundle(NodeBundle { color: Color::GRAY.into(), @@ -33,9 +33,7 @@ fn setup(mut commands: Commands) { ..default() }) .with_children(|parent| { - // spawn a node with no z-index. - // this has the same result as using `ZIndex::Local(0)`. - // this uses the default depth ordering which is based on where this node is in the hierarchy. + // spawn a node with default z-index. parent.spawn_bundle(NodeBundle { color: Color::RED.into(), style: Style { @@ -53,80 +51,76 @@ fn setup(mut commands: Commands) { // spawn a node with a positive local z-index of 2. // it will show above other nodes in the grey container. - parent - .spawn_bundle(NodeBundle { - color: Color::BLUE.into(), - style: Style { - position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(45.0), - bottom: Val::Px(30.0), - ..default() - }, - size: Size::new(Val::Px(100.0), Val::Px(50.0)), + parent.spawn_bundle(NodeBundle { + z_index: ZIndex::Local(2), + color: Color::BLUE.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(45.0), + bottom: Val::Px(30.0), ..default() }, + size: Size::new(Val::Px(100.0), Val::Px(50.0)), ..default() - }) - .insert(ZIndex::Local(2)); + }, + ..default() + }); // spawn a node with a negative local z-index. - // it will show under all other nodes in the grey container. - parent - .spawn_bundle(NodeBundle { - color: Color::GREEN.into(), - style: Style { - position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(70.0), - bottom: Val::Px(20.0), - ..default() - }, - size: Size::new(Val::Px(100.0), Val::Px(75.0)), + // it will show under other nodes in the grey container. + parent.spawn_bundle(NodeBundle { + z_index: ZIndex::Local(-1), + color: Color::GREEN.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(70.0), + bottom: Val::Px(20.0), ..default() }, + size: Size::new(Val::Px(100.0), Val::Px(75.0)), ..default() - }) - .insert(ZIndex::Local(-1)); + }, + ..default() + }); // spawn a node with a positive global z-index of 1. - // it will show above all other nodes, because it's the highest global z-index in this example app. - // by default, boxes all share the global z-index of 0 that the grey container is implicitly added to. - parent - .spawn_bundle(NodeBundle { - color: Color::PURPLE.into(), - style: Style { - position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(15.0), - bottom: Val::Px(-5.0), - ..default() - }, - size: Size::new(Val::Px(100.0), Val::Px(60.0)), + // it will show above all other nodes, because it's the highest global z-index in this example. + // by default, boxes all share the global z-index of 0 that the grey container is added to. + parent.spawn_bundle(NodeBundle { + z_index: ZIndex::Global(1), + color: Color::PURPLE.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(15.0), + bottom: Val::Px(10.0), ..default() }, + size: Size::new(Val::Px(100.0), Val::Px(60.0)), ..default() - }) - .insert(ZIndex::Global(1)); + }, + ..default() + }); - // spawn a node with a negative global z-index. - // this will show under all other nodes, including its parent, because -1 is the lowest global z-index - // in this example app. - parent - .spawn_bundle(NodeBundle { - color: Color::YELLOW.into(), - style: Style { - position_type: PositionType::Absolute, - position: UiRect { - left: Val::Px(-10.0), - bottom: Val::Px(-10.0), - ..default() - }, - size: Size::new(Val::Px(100.0), Val::Px(50.0)), + // spawn a node with a negative global z-index of -1. + // this will show under all other nodes including its parent, because it's the lowest global z-index + // in this example. + parent.spawn_bundle(NodeBundle { + z_index: ZIndex::Global(-1), + color: Color::YELLOW.into(), + style: Style { + position_type: PositionType::Absolute, + position: UiRect { + left: Val::Px(-15.0), + bottom: Val::Px(-15.0), ..default() }, + size: Size::new(Val::Px(100.0), Val::Px(125.0)), ..default() - }) - .insert(ZIndex::Global(-1)); + }, + ..default() + }); }); } From c4a3f48d77b66057b3c6cdad22ea9577fa317df0 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Wed, 7 Sep 2022 23:40:19 -0400 Subject: [PATCH 09/27] Add unit test for the UI Stack system --- crates/bevy_ui/src/stack.rs | 118 ++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 1a457d0abd816..8f85caa61490c 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -102,3 +102,121 @@ fn fill_stack_recursively(result: &mut Vec, stack: &mut StackingContext) fill_stack_recursively(result, &mut entry.stack); } } + +#[cfg(test)] +mod tests { + use bevy_ecs::{ + component::Component, + schedule::{Schedule, Stage, SystemStage}, + system::{CommandQueue, Commands}, + world::World, + }; + use bevy_hierarchy::BuildChildren; + + use crate::{Node, UiStack, ZIndex}; + + use super::ui_stack_system; + + #[derive(Component, PartialEq, Debug, Clone)] + struct Label(&'static str); + + fn node_with_zindex(name: &'static str, z_index: ZIndex) -> (Label, Node, ZIndex) { + (Label(name), Node::default(), z_index) + } + + fn node_without_zindex(name: &'static str) -> (Label, Node) { + (Label(name), Node::default()) + } + + /// Tests the UI Stack system. + /// + /// This tests for siblings default ordering according to their insertion order, but it + /// can't test the same thing for UI roots. UI roots having no parents, they do not have + /// a stable ordering that we can test against. If we test it, it may pass now and start + /// failing randomly in the future because of some unrelated bevy_ecs change. + #[test] + fn test_ui_stack_system() { + let mut world = World::default(); + world.init_resource::(); + + let mut queue = CommandQueue::default(); + let mut commands = Commands::new(&mut queue, &world); + commands.spawn_bundle(node_with_zindex("0", ZIndex::Global(2))); + + commands + .spawn_bundle(node_with_zindex("1", ZIndex::Local(1))) + .with_children(|parent| { + parent + .spawn_bundle(node_without_zindex("1-0")) + .with_children(|parent| { + parent.spawn_bundle(node_without_zindex("1-0-0")); + parent.spawn_bundle(node_without_zindex("1-0-1")); + parent.spawn_bundle(node_with_zindex("1-0-2", ZIndex::Local(-1))); + }); + parent.spawn_bundle(node_without_zindex("1-1")); + parent + .spawn_bundle(node_with_zindex("1-2", ZIndex::Global(-1))) + .with_children(|parent| { + parent.spawn_bundle(node_without_zindex("1-2-0")); + parent.spawn_bundle(node_with_zindex("1-2-1", ZIndex::Global(-3))); + parent + .spawn_bundle(node_without_zindex("1-2-2")) + .with_children(|_| ()); + parent.spawn_bundle(node_without_zindex("1-2-3")); + }); + parent.spawn_bundle(node_without_zindex("1-3")); + }); + + commands + .spawn_bundle(node_without_zindex("2")) + .with_children(|parent| { + parent + .spawn_bundle(node_without_zindex("2-0")) + .with_children(|_parent| ()); + parent + .spawn_bundle(node_without_zindex("2-1")) + .with_children(|parent| { + parent.spawn_bundle(node_without_zindex("2-1-0")); + }); + }); + + commands.spawn_bundle(node_with_zindex("3", ZIndex::Global(-2))); + + queue.apply(&mut world); + + let mut schedule = Schedule::default(); + let mut update_stage = SystemStage::parallel(); + update_stage.add_system(ui_stack_system); + schedule.add_stage("update", update_stage); + schedule.run(&mut world); + + let mut query = world.query::<&Label>(); + let ui_stack = world.resource::(); + let actual_result = ui_stack + .uinodes + .iter() + .map(|entity| query.get(&world, *entity).unwrap().clone()) + .collect::>(); + let expected_result = vec![ + (Label("1-2-1")), // ZIndex::Global(-3) + (Label("3")), // ZIndex::Global(-2) + (Label("1-2")), // ZIndex::Global(-1) + (Label("1-2-0")), + (Label("1-2-2")), + (Label("1-2-3")), + (Label("2")), + (Label("2-0")), + (Label("2-1")), + (Label("2-1-0")), + (Label("1")), // ZIndex::Local(1) + (Label("1-0")), + (Label("1-0-2")), // ZIndex::Local(-1) + (Label("1-0-0")), + (Label("1-0-1")), + (Label("1-1")), + (Label("1-3")), + (Label("0")), // ZIndex::Global(2) + ]; + assert_eq!(actual_result, expected_result); + } +} From 34fd3a2496dfc3b8630638c5a068183965c3e7e7 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Thu, 8 Sep 2022 00:08:32 -0400 Subject: [PATCH 10/27] Fix clippy errors --- crates/bevy_ui/src/stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 8f85caa61490c..0d5732556f640 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -133,7 +133,7 @@ mod tests { /// This tests for siblings default ordering according to their insertion order, but it /// can't test the same thing for UI roots. UI roots having no parents, they do not have /// a stable ordering that we can test against. If we test it, it may pass now and start - /// failing randomly in the future because of some unrelated bevy_ecs change. + /// failing randomly in the future because of some unrelated `bevy_ecs` change. #[test] fn test_ui_stack_system() { let mut world = World::default(); From e0690a6f4196169ff73f3fd8a7b10b088feabb24 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Thu, 8 Sep 2022 22:22:36 -0400 Subject: [PATCH 11/27] Improvements based on review comments --- Cargo.toml | 2 +- crates/bevy_ui/src/focus.rs | 65 +++++++++++++++++++---------------- crates/bevy_ui/src/ui_node.rs | 21 +++++++---- examples/README.md | 2 +- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d3ca72db4a84a..a6d63ef69e80f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1447,7 +1447,7 @@ name = "z_index" path = "examples/ui/z_index.rs" [package.metadata.example.z_index] -name = "UI Z-INDEX" +name = "UI Z-Index" description = "Demonstrates z-index for UI" category = "UI (User Interface)" wasm = true diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 511aed3078deb..e2cd17f0e8aa2 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -2,6 +2,7 @@ use crate::{entity::UiCameraConfig, CalculatedClip, Node, UiStack}; use bevy_ecs::{ entity::Entity, prelude::Component, + query::WorldQuery, reflect::ReflectComponent, system::{Local, Query, Res}, }; @@ -61,6 +62,19 @@ pub struct State { entities_to_reset: SmallVec<[Entity; 1]>, } +/// Main query for the focus system +#[derive(WorldQuery)] +#[world_query(mutable)] +pub struct NodeQuery { + entity: Entity, + node: &'static Node, + global_transform: &'static GlobalTransform, + interaction: Option<&'static mut Interaction>, + focus_policy: Option<&'static FocusPolicy>, + calculated_clip: Option<&'static CalculatedClip>, + computed_visibility: Option<&'static ComputedVisibility>, +} + /// The system that sets Interaction for all UI elements based on the mouse cursor activity /// /// Entities with a hidden [`ComputedVisibility`] are always treated as released. @@ -71,15 +85,7 @@ pub fn ui_focus_system( mouse_button_input: Res>, touches_input: Res, ui_stack: Res, - mut node_query: Query<( - Entity, - &Node, - &GlobalTransform, - Option<&mut Interaction>, - Option<&FocusPolicy>, - Option<&CalculatedClip>, - Option<&ComputedVisibility>, - )>, + mut node_query: Query, ) { // reset entities that were both clicked and released in the last frame for entity in state.entities_to_reset.drain(..) { @@ -91,10 +97,8 @@ pub fn ui_focus_system( let mouse_released = mouse_button_input.just_released(MouseButton::Left) || touches_input.any_just_released(); if mouse_released { - for (_entity, _node, _global_transform, interaction, _focus_policy, _clip, _visibility) in - node_query.iter_mut() - { - if let Some(mut interaction) = interaction { + for node in node_query.iter_mut() { + if let Some(mut interaction) = node.interaction { if *interaction == Interaction::Clicked { *interaction = Interaction::None; } @@ -126,16 +130,15 @@ pub fn ui_focus_system( let moused_over_nodes = ui_stack .uinodes .iter() + // reverse the iterator to traverse the tree from closest nodes to furthest .rev() .filter_map(|entity| { - if let Ok((entity, node, global_transform, interaction, _, clip, visibility)) = - node_query.get_mut(*entity) - { + if let Ok(node) = node_query.get_mut(*entity) { // Nodes that are not rendered should not be interactable - if let Some(computed_visibility) = visibility { + if let Some(computed_visibility) = node.computed_visibility { if !computed_visibility.is_visible() { // Reset their interaction to None to avoid strange stuck state - if let Some(mut interaction) = interaction { + if let Some(mut interaction) = node.interaction { // We cannot simply set the interaction to None, as that will trigger change detection repeatedly if *interaction != Interaction::None { *interaction = Interaction::None; @@ -146,12 +149,12 @@ pub fn ui_focus_system( } } - let position = global_transform.translation(); + let position = node.global_transform.translation(); let ui_position = position.truncate(); - let extents = node.size / 2.0; + let extents = node.node.size / 2.0; let mut min = ui_position - extents; let mut max = ui_position + extents; - if let Some(clip) = clip { + if let Some(clip) = node.calculated_clip { min = Vec2::max(min, clip.clip.min); max = Vec2::min(max, clip.clip.max); } @@ -166,7 +169,7 @@ pub fn ui_focus_system( if contains_cursor { return Some(entity); - } else if let Some(mut interaction) = interaction { + } else if let Some(mut interaction) = node.interaction { if *interaction == Interaction::Hovered || (cursor_position.is_none() && *interaction != Interaction::None) { @@ -181,8 +184,8 @@ pub fn ui_focus_system( // set Clicked or Hovered on top nodes for entity in &moused_over_nodes { - if let Ok((_, _, _, interaction, focus_policy, _, _)) = node_query.get_mut(*entity) { - if let Some(mut interaction) = interaction { + if let Ok(node) = node_query.get_mut(**entity) { + if let Some(mut interaction) = node.interaction { if mouse_clicked { // only consider nodes with Interaction "clickable" if *interaction != Interaction::Clicked { @@ -190,7 +193,7 @@ pub fn ui_focus_system( // if the mouse was simultaneously released, reset this Interaction in the next // frame if mouse_released { - state.entities_to_reset.push(*entity); + state.entities_to_reset.push(**entity); } } } else if *interaction == Interaction::None { @@ -198,7 +201,7 @@ pub fn ui_focus_system( } } - match focus_policy.cloned().unwrap_or(FocusPolicy::Block) { + match node.focus_policy.cloned().unwrap_or(FocusPolicy::Block) { FocusPolicy::Block => { break; } @@ -208,10 +211,12 @@ pub fn ui_focus_system( } // reset lower nodes to None for entity in &moused_over_nodes { - if let Ok((_, _, _, Some(mut interaction), _, _, _)) = node_query.get_mut(*entity) { - // don't reset clicked nodes because they're handled separately - if *interaction != Interaction::Clicked && *interaction != Interaction::None { - *interaction = Interaction::None; + if let Ok(node) = node_query.get_mut(**entity) { + if let Some(mut interaction) = node.interaction { + // don't reset clicked nodes because they're handled separately + if *interaction != Interaction::Clicked && *interaction != Interaction::None { + *interaction = Interaction::None; + } } } } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 191135a82d8c2..01c6f2f2d2979 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -411,18 +411,25 @@ pub struct CalculatedClip { pub clip: Rect, } -/// Indicates that this node has special requirements for the order of depth in which -/// it should appear in the UI instead of relying solely on the hierarchy of the UI. +/// Indicates that this [`Node`] entity's front-to-back ordering is not controlled solely +/// by its location in the UI hierarchy. A node with a higher z-index will appear on top +/// of other nodes with a lower z-index. /// -/// Omitting this component on a node will yield the same result as using [`ZIndex::Local(0)`]. +/// UI nodes that have the same z-index will appear according to the order in which they +/// appear in the UI hierarchy. In such a case, the last node to be added to its parent +/// will appear in front of this parent's other children. +/// +/// Internally, nodes with a global z-index share the stacking context of root UI nodes +/// (nodes that have no parent). Because of this, there is no difference between using +/// [`ZIndex::Local(n)`] and [`ZIndex::Global(n)`] for root nodes. +/// +/// Not inserting this component on a node will give the same result as using [`ZIndex::Local(0)`]. #[derive(Component, Copy, Clone, Debug, Reflect)] pub enum ZIndex { /// Indicates the order in which this node should be rendered relative to its siblings. - /// - /// For root UI nodes (nodes that have to parent), using this and [`ZIndex::Global`] is equivalent. Local(i32), - /// Indicates the order in which this node should be rendered relative to all other nodes - /// that also use have a [`ZIndex::Global`] value. + /// Indicates the order in which this node should be rendered relative to root nodes and + /// all other nodes that have a global z-index. Global(i32), } diff --git a/examples/README.md b/examples/README.md index 281b3f450cf20..a9fa6d3044031 100644 --- a/examples/README.md +++ b/examples/README.md @@ -314,7 +314,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/scaling.rs) | Illustrates how to scale the UI -[UI Z-INDEX](../examples/ui/z_index.rs) | Demonstrates z-index for UI +[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates z-index for UI ## Window From fff9b33f0238f3cfba17f08d53c961e6b7fb92a6 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Thu, 8 Sep 2022 22:59:07 -0400 Subject: [PATCH 12/27] Use iter_many in focus system to improve perfs and readability --- crates/bevy_ui/src/focus.rs | 55 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index e2cd17f0e8aa2..83664ac9ee783 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -168,7 +168,7 @@ pub fn ui_focus_system( }; if contains_cursor { - return Some(entity); + return Some(*entity); } else if let Some(mut interaction) = node.interaction { if *interaction == Interaction::Hovered || (cursor_position.is_none() && *interaction != Interaction::None) @@ -176,47 +176,44 @@ pub fn ui_focus_system( *interaction = Interaction::None; } } - return None; } None }) - .collect::>(); + .collect::>(); // set Clicked or Hovered on top nodes - for entity in &moused_over_nodes { - if let Ok(node) = node_query.get_mut(**entity) { - if let Some(mut interaction) = node.interaction { - if mouse_clicked { - // only consider nodes with Interaction "clickable" - if *interaction != Interaction::Clicked { - *interaction = Interaction::Clicked; - // if the mouse was simultaneously released, reset this Interaction in the next - // frame - if mouse_released { - state.entities_to_reset.push(**entity); - } + let mut iter = node_query.iter_many_mut(&moused_over_nodes); + while let Some(node) = iter.fetch_next() { + if let Some(mut interaction) = node.interaction { + if mouse_clicked { + // only consider nodes with Interaction "clickable" + if *interaction != Interaction::Clicked { + *interaction = Interaction::Clicked; + // if the mouse was simultaneously released, reset this Interaction in the next + // frame + if mouse_released { + state.entities_to_reset.push(node.entity); } - } else if *interaction == Interaction::None { - *interaction = Interaction::Hovered; } + } else if *interaction == Interaction::None { + *interaction = Interaction::Hovered; } + } - match node.focus_policy.cloned().unwrap_or(FocusPolicy::Block) { - FocusPolicy::Block => { - break; - } - FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ } + match node.focus_policy.cloned().unwrap_or(FocusPolicy::Block) { + FocusPolicy::Block => { + break; } + FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ } } } // reset lower nodes to None - for entity in &moused_over_nodes { - if let Ok(node) = node_query.get_mut(**entity) { - if let Some(mut interaction) = node.interaction { - // don't reset clicked nodes because they're handled separately - if *interaction != Interaction::Clicked && *interaction != Interaction::None { - *interaction = Interaction::None; - } + let mut iter = node_query.iter_many_mut(&moused_over_nodes); + while let Some(node) = iter.fetch_next() { + if let Some(mut interaction) = node.interaction { + // don't reset clicked nodes because they're handled separately + if *interaction != Interaction::Clicked && *interaction != Interaction::None { + *interaction = Interaction::None; } } } From 4e6a193e202ce9f98fe8fb02579a787cc655605f Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Thu, 8 Sep 2022 23:04:09 -0400 Subject: [PATCH 13/27] Use iter_many in UI render extraction --- crates/bevy_ui/src/render/mod.rs | 146 +++++++++++++++---------------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 5304a95a5add1..a4111b9e3262c 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -190,33 +190,33 @@ pub fn extract_uinodes( >, ) { extracted_uinodes.uinodes.clear(); - for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, transform, color, image, visibility, clip)) = uinode_query.get(*entity) { - if !visibility.is_visible() { - continue; - } - let image = image.0.clone_weak(); - // Skip loading images - if !images.contains(&image) { - continue; - } - // Skip completely transparent nodes - if color.0.a() == 0.0 { - continue; - } - extracted_uinodes.uinodes.push(ExtractedUiNode { - stack_index, - transform: transform.compute_matrix(), - color: color.0, - rect: Rect { - min: Vec2::ZERO, - max: uinode.size, - }, - image, - atlas_size: None, - clip: clip.map(|clip| clip.clip), - }); + for (stack_index, (uinode, transform, color, image, visibility, clip)) in + uinode_query.iter_many(&ui_stack.uinodes).enumerate() + { + if !visibility.is_visible() { + continue; + } + let image = image.0.clone_weak(); + // Skip loading images + if !images.contains(&image) { + continue; + } + // Skip completely transparent nodes + if color.0.a() == 0.0 { + continue; } + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: transform.compute_matrix(), + color: color.0, + rect: Rect { + min: Vec2::ZERO, + max: uinode.size, + }, + image, + atlas_size: None, + clip: clip.map(|clip| clip.clip), + }); } } @@ -291,55 +291,53 @@ pub fn extract_text_uinodes( >, ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) = - uinode_query.get(*entity) - { - 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 { - continue; - } - let text_glyphs = &text_layout_info.glyphs; - let alignment_offset = (uinode.size / -2.0).extend(0.0); - - let mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; - } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let texture = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - let rect = atlas.textures[index]; - let atlas_size = Some(atlas.size); - - // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` - let extracted_transform = global_transform.compute_matrix() - * Mat4::from_scale(Vec3::splat(scale_factor.recip())) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); - - extracted_uinodes.uinodes.push(ExtractedUiNode { - stack_index, - transform: extracted_transform, - color, - rect, - image: texture, - atlas_size, - clip: clip.map(|clip| clip.clip), - }); + for (stack_index, (uinode, global_transform, text, text_layout_info, visibility, clip)) in + uinode_query.iter_many(&ui_stack.uinodes).enumerate() + { + 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 { + continue; + } + let text_glyphs = &text_layout_info.glyphs; + let alignment_offset = (uinode.size / -2.0).extend(0.0); + + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let texture = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = atlas.textures[index]; + let atlas_size = Some(atlas.size); + + // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` + let extracted_transform = global_transform.compute_matrix() + * Mat4::from_scale(Vec3::splat(scale_factor.recip())) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: extracted_transform, + color, + rect, + image: texture, + atlas_size, + clip: clip.map(|clip| clip.clip), + }); } } } From bc7c725467b53af0d8991da53146a5ac03ef7d02 Mon Sep 17 00:00:00 2001 From: Oceantume Date: Fri, 9 Sep 2022 15:52:36 -0400 Subject: [PATCH 14/27] Revert "Use iter_many in UI render extraction" This reverts commit 4e6a193e202ce9f98fe8fb02579a787cc655605f. --- crates/bevy_ui/src/render/mod.rs | 146 ++++++++++++++++--------------- 1 file changed, 74 insertions(+), 72 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index a4111b9e3262c..5304a95a5add1 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -190,33 +190,33 @@ pub fn extract_uinodes( >, ) { extracted_uinodes.uinodes.clear(); - for (stack_index, (uinode, transform, color, image, visibility, clip)) in - uinode_query.iter_many(&ui_stack.uinodes).enumerate() - { - if !visibility.is_visible() { - continue; - } - let image = image.0.clone_weak(); - // Skip loading images - if !images.contains(&image) { - continue; - } - // Skip completely transparent nodes - if color.0.a() == 0.0 { - continue; + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((uinode, transform, color, image, visibility, clip)) = uinode_query.get(*entity) { + if !visibility.is_visible() { + continue; + } + let image = image.0.clone_weak(); + // Skip loading images + if !images.contains(&image) { + continue; + } + // Skip completely transparent nodes + if color.0.a() == 0.0 { + continue; + } + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: transform.compute_matrix(), + color: color.0, + rect: Rect { + min: Vec2::ZERO, + max: uinode.size, + }, + image, + atlas_size: None, + clip: clip.map(|clip| clip.clip), + }); } - extracted_uinodes.uinodes.push(ExtractedUiNode { - stack_index, - transform: transform.compute_matrix(), - color: color.0, - rect: Rect { - min: Vec2::ZERO, - max: uinode.size, - }, - image, - atlas_size: None, - clip: clip.map(|clip| clip.clip), - }); } } @@ -291,53 +291,55 @@ pub fn extract_text_uinodes( >, ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (stack_index, (uinode, global_transform, text, text_layout_info, visibility, clip)) in - uinode_query.iter_many(&ui_stack.uinodes).enumerate() - { - 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 { - continue; - } - let text_glyphs = &text_layout_info.glyphs; - let alignment_offset = (uinode.size / -2.0).extend(0.0); - - let mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) = + uinode_query.get(*entity) + { + 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 { + continue; + } + let text_glyphs = &text_layout_info.glyphs; + let alignment_offset = (uinode.size / -2.0).extend(0.0); + + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; + } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let texture = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = atlas.textures[index]; + let atlas_size = Some(atlas.size); + + // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` + let extracted_transform = global_transform.compute_matrix() + * Mat4::from_scale(Vec3::splat(scale_factor.recip())) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: extracted_transform, + color, + rect, + image: texture, + atlas_size, + clip: clip.map(|clip| clip.clip), + }); } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let texture = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - let rect = atlas.textures[index]; - let atlas_size = Some(atlas.size); - - // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` - let extracted_transform = global_transform.compute_matrix() - * Mat4::from_scale(Vec3::splat(scale_factor.recip())) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); - - extracted_uinodes.uinodes.push(ExtractedUiNode { - stack_index, - transform: extracted_transform, - color, - rect, - image: texture, - atlas_size, - clip: clip.map(|clip| clip.clip), - }); } } } From d11dbb55b5c49b64a09da1f58dac21e91f138fbe Mon Sep 17 00:00:00 2001 From: Oceantume Date: Fri, 9 Sep 2022 16:11:24 -0400 Subject: [PATCH 15/27] Fix hover state by restoring the mutable iterator logic --- crates/bevy_ui/src/focus.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 83664ac9ee783..8680aa04265f7 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -168,21 +168,27 @@ pub fn ui_focus_system( }; if contains_cursor { - return Some(*entity); - } else if let Some(mut interaction) = node.interaction { - if *interaction == Interaction::Hovered - || (cursor_position.is_none() && *interaction != Interaction::None) - { - *interaction = Interaction::None; + Some(*entity) + } else { + if let Some(mut interaction) = node.interaction { + if *interaction == Interaction::Hovered + || (cursor_position.is_none() && *interaction != Interaction::None) + { + *interaction = Interaction::None; + } } + None } + } else { + None } - None }) .collect::>(); + let mut moused_over_nodes = moused_over_nodes.into_iter(); + // set Clicked or Hovered on top nodes - let mut iter = node_query.iter_many_mut(&moused_over_nodes); + let mut iter = node_query.iter_many_mut(moused_over_nodes.by_ref()); while let Some(node) = iter.fetch_next() { if let Some(mut interaction) = node.interaction { if mouse_clicked { @@ -208,7 +214,7 @@ pub fn ui_focus_system( } } // reset lower nodes to None - let mut iter = node_query.iter_many_mut(&moused_over_nodes); + let mut iter = node_query.iter_many_mut(moused_over_nodes); while let Some(node) = iter.fetch_next() { if let Some(mut interaction) = node.interaction { // don't reset clicked nodes because they're handled separately From 29bf401df9035b0d0a1342045f27aa788ba5cf1d Mon Sep 17 00:00:00 2001 From: Oceantume Date: Fri, 9 Sep 2022 16:15:49 -0400 Subject: [PATCH 16/27] Fix small issues from comments --- crates/bevy_ui/src/focus.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 8680aa04265f7..f75aa0c7871de 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -62,7 +62,7 @@ pub struct State { entities_to_reset: SmallVec<[Entity; 1]>, } -/// Main query for the focus system +/// Main query for [`ui_focus_system`] #[derive(WorldQuery)] #[world_query(mutable)] pub struct NodeQuery { @@ -206,7 +206,7 @@ pub fn ui_focus_system( } } - match node.focus_policy.cloned().unwrap_or(FocusPolicy::Block) { + match node.focus_policy.unwrap_or(&FocusPolicy::Block) { FocusPolicy::Block => { break; } From e1d58fee46fcd5abcc13a44d3903e003ba4c03d1 Mon Sep 17 00:00:00 2001 From: Oceantume Date: Fri, 9 Sep 2022 16:22:09 -0400 Subject: [PATCH 17/27] =?UTF-8?q?Fix=20clippy=20error=20=F0=9F=92=A6?= =?UTF-8?q?=F0=9F=92=A6=F0=9F=92=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bevy_ui/src/focus.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index f75aa0c7871de..75af8028fa521 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -127,7 +127,7 @@ pub fn ui_focus_system( .find_map(|window| window.cursor_position()) .or_else(|| touches_input.first_pressed_position()); - let moused_over_nodes = ui_stack + let mut moused_over_nodes = ui_stack .uinodes .iter() // reverse the iterator to traverse the tree from closest nodes to furthest @@ -183,9 +183,8 @@ pub fn ui_focus_system( None } }) - .collect::>(); - - let mut moused_over_nodes = moused_over_nodes.into_iter(); + .collect::>() + .into_iter(); // set Clicked or Hovered on top nodes let mut iter = node_query.iter_many_mut(moused_over_nodes.by_ref()); From 775ebc600d61a6c78a047709eddea274651a9daa Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Mon, 19 Sep 2022 23:00:13 -0400 Subject: [PATCH 18/27] Add comments to clarify the interaction & focus system --- crates/bevy_ui/src/focus.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 75af8028fa521..40e637f745a89 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -127,6 +127,9 @@ pub fn ui_focus_system( .find_map(|window| window.cursor_position()) .or_else(|| touches_input.first_pressed_position()); + // prepare an iterator that contains all the nodes that have the cursor in their rect, + // from the top node to the bottom one. this will also reset the interaction to `None` + // for all nodes encountered that are no longer hovered. let mut moused_over_nodes = ui_stack .uinodes .iter() @@ -186,7 +189,8 @@ pub fn ui_focus_system( .collect::>() .into_iter(); - // set Clicked or Hovered on top nodes + // set Clicked or Hovered on top nodes. as soon as a node with a `Block` focus policy is detected, + // the iteration will stop on it because it "captures" the interaction. let mut iter = node_query.iter_many_mut(moused_over_nodes.by_ref()); while let Some(node) = iter.fetch_next() { if let Some(mut interaction) = node.interaction { @@ -212,7 +216,8 @@ pub fn ui_focus_system( FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ } } } - // reset lower nodes to None + // reset `Interaction` for the remaining lower nodes to `None`. those are the nodes that remain in + // `moused_over_nodes` after the previous loop is exited. let mut iter = node_query.iter_many_mut(moused_over_nodes); while let Some(node) = iter.fetch_next() { if let Some(mut interaction) = node.interaction { From 2b5867708d9016f0b95b71caf7480623408201c3 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Tue, 20 Sep 2022 09:07:40 -0400 Subject: [PATCH 19/27] Update examples/ui/z_index.rs Co-authored-by: Alice Cecile --- examples/ui/z_index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 02508a7493b63..5951cb440c9d9 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -1,4 +1,4 @@ -//! Demonstrates how to use z-index +//! Demonstrates how to use the z-index component on UI nodes to control their relative depth //! //! It uses colored boxes with different z-index values to demonstrate how it can affect the order of //! depth of nodes compared to their siblings, but also compared to the entire UI. From 791b677ab89409df81af4884be029d232d01cbfc Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Tue, 20 Sep 2022 09:07:47 -0400 Subject: [PATCH 20/27] Update examples/README.md Co-authored-by: Alice Cecile --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index a9fa6d3044031..6d2fe4b82059b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -314,7 +314,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/scaling.rs) | Illustrates how to scale the UI -[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates z-index for UI +[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements ## Window From 6a70d96e1ad00783e34144e25ac723739ac7102e Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Tue, 20 Sep 2022 09:07:52 -0400 Subject: [PATCH 21/27] Update crates/bevy_ui/src/ui_node.rs Co-authored-by: Alice Cecile --- crates/bevy_ui/src/ui_node.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 01c6f2f2d2979..e4f34b2ed72b2 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -423,7 +423,7 @@ pub struct CalculatedClip { /// (nodes that have no parent). Because of this, there is no difference between using /// [`ZIndex::Local(n)`] and [`ZIndex::Global(n)`] for root nodes. /// -/// Not inserting this component on a node will give the same result as using [`ZIndex::Local(0)`]. +/// Nodes without this component will be treated as if they had a value of [`ZIndex::Local(0)`]. #[derive(Component, Copy, Clone, Debug, Reflect)] pub enum ZIndex { /// Indicates the order in which this node should be rendered relative to its siblings. From a4d8e7bc384827094bb8e25d8b2c46e79c0d4be1 Mon Sep 17 00:00:00 2001 From: Oceantume Date: Tue, 20 Sep 2022 09:16:03 -0400 Subject: [PATCH 22/27] Update examples readme --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 6d2fe4b82059b..a9fa6d3044031 100644 --- a/examples/README.md +++ b/examples/README.md @@ -314,7 +314,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/scaling.rs) | Illustrates how to scale the UI -[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements +[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates z-index for UI ## Window From 34f97906bc91b728cd2c23fd88433f2c97e1dcb8 Mon Sep 17 00:00:00 2001 From: Oceantume Date: Tue, 20 Sep 2022 09:23:57 -0400 Subject: [PATCH 23/27] Properly update example description --- Cargo.toml | 2 +- examples/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6d63ef69e80f..2b4ef7862d31d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1448,7 +1448,7 @@ path = "examples/ui/z_index.rs" [package.metadata.example.z_index] name = "UI Z-Index" -description = "Demonstrates z-index for UI" +description = "Demonstrates how to control the relative depth (z-position) of UI elements" category = "UI (User Interface)" wasm = true diff --git a/examples/README.md b/examples/README.md index a9fa6d3044031..6d2fe4b82059b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -314,7 +314,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/scaling.rs) | Illustrates how to scale the UI -[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates z-index for UI +[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements ## Window From c0ff6f5de1759b9d0f264834b041287f108bd853 Mon Sep 17 00:00:00 2001 From: Oceantume Date: Wed, 21 Sep 2022 09:13:56 -0400 Subject: [PATCH 24/27] Remove unused component from z_index example --- examples/ui/z_index.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 5951cb440c9d9..2054a17d4850f 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -13,9 +13,6 @@ fn main() { .run(); } -#[derive(Component)] -struct ZIndexText; - fn setup(mut commands: Commands) { commands.spawn_bundle(Camera2dBundle::default()); From 36dfd25f1fbc8c528063b3a612b237faf9dd0034 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Mon, 31 Oct 2022 18:44:57 -0400 Subject: [PATCH 25/27] Fix format --- crates/bevy_ui/src/entity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index eb43b9c31a6a5..129a3d72382ab 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,7 +2,7 @@ use crate::{ widget::{Button, ImageMode}, - BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex + BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex, }; use bevy_ecs::{ bundle::Bundle, From 32766b3c9b948084fa0fe948d06c140a9c57f1ae Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Mon, 31 Oct 2022 18:56:20 -0400 Subject: [PATCH 26/27] Fix example and reuse uinodes vector --- crates/bevy_ui/src/stack.rs | 42 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 0d5732556f640..4df23559b8d6a 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -46,10 +46,8 @@ pub fn ui_stack_system( ); } - *ui_stack = UiStack { - uinodes: Vec::::with_capacity(total_entry_count), - }; - + ui_stack.uinodes.clear(); + ui_stack.uinodes.reserve(total_entry_count); fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context); } @@ -141,46 +139,46 @@ mod tests { let mut queue = CommandQueue::default(); let mut commands = Commands::new(&mut queue, &world); - commands.spawn_bundle(node_with_zindex("0", ZIndex::Global(2))); + commands.spawn(node_with_zindex("0", ZIndex::Global(2))); commands - .spawn_bundle(node_with_zindex("1", ZIndex::Local(1))) + .spawn(node_with_zindex("1", ZIndex::Local(1))) .with_children(|parent| { parent - .spawn_bundle(node_without_zindex("1-0")) + .spawn(node_without_zindex("1-0")) .with_children(|parent| { - parent.spawn_bundle(node_without_zindex("1-0-0")); - parent.spawn_bundle(node_without_zindex("1-0-1")); - parent.spawn_bundle(node_with_zindex("1-0-2", ZIndex::Local(-1))); + parent.spawn(node_without_zindex("1-0-0")); + parent.spawn(node_without_zindex("1-0-1")); + parent.spawn(node_with_zindex("1-0-2", ZIndex::Local(-1))); }); - parent.spawn_bundle(node_without_zindex("1-1")); + parent.spawn(node_without_zindex("1-1")); parent - .spawn_bundle(node_with_zindex("1-2", ZIndex::Global(-1))) + .spawn(node_with_zindex("1-2", ZIndex::Global(-1))) .with_children(|parent| { - parent.spawn_bundle(node_without_zindex("1-2-0")); - parent.spawn_bundle(node_with_zindex("1-2-1", ZIndex::Global(-3))); + parent.spawn(node_without_zindex("1-2-0")); + parent.spawn(node_with_zindex("1-2-1", ZIndex::Global(-3))); parent - .spawn_bundle(node_without_zindex("1-2-2")) + .spawn(node_without_zindex("1-2-2")) .with_children(|_| ()); - parent.spawn_bundle(node_without_zindex("1-2-3")); + parent.spawn(node_without_zindex("1-2-3")); }); - parent.spawn_bundle(node_without_zindex("1-3")); + parent.spawn(node_without_zindex("1-3")); }); commands - .spawn_bundle(node_without_zindex("2")) + .spawn(node_without_zindex("2")) .with_children(|parent| { parent - .spawn_bundle(node_without_zindex("2-0")) + .spawn(node_without_zindex("2-0")) .with_children(|_parent| ()); parent - .spawn_bundle(node_without_zindex("2-1")) + .spawn(node_without_zindex("2-1")) .with_children(|parent| { - parent.spawn_bundle(node_without_zindex("2-1-0")); + parent.spawn(node_without_zindex("2-1-0")); }); }); - commands.spawn_bundle(node_with_zindex("3", ZIndex::Global(-2))); + commands.spawn(node_with_zindex("3", ZIndex::Global(-2))); queue.apply(&mut world); From 2f793cc0ac6af2d977ddefddf4ff5dd0bf97a017 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 2 Nov 2022 15:04:59 -0700 Subject: [PATCH 27/27] Fix merge errors --- crates/bevy_ui/src/entity.rs | 1 + crates/bevy_ui/src/focus.rs | 2 +- crates/bevy_ui/src/render/mod.rs | 4 ++-- examples/ui/z_index.rs | 26 +++++++++++++------------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 129a3d72382ab..1e3491ad14d27 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -62,6 +62,7 @@ impl Default for NodeBundle { global_transform: Default::default(), visibility: Default::default(), computed_visibility: Default::default(), + z_index: Default::default(), } } } diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 4cbef3a8d10e6..423711d6e39ff 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -154,7 +154,7 @@ pub fn ui_focus_system( let position = node.global_transform.translation(); let ui_position = position.truncate(); - let extents = node.calculated_size / 2.0; + let extents = node.node.size() / 2.0; let mut min = ui_position - extents; let mut max = ui_position + extents; if let Some(clip) = node.calculated_clip { diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index fe9e4e3bcea4b..51418b6b448c4 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -329,11 +329,11 @@ pub fn extract_text_uinodes( continue; } // Skip if size is set to zero (e.g. when a parent is set to `Display::None`) - if uinode.size == Vec2::ZERO { + if uinode.size() == Vec2::ZERO { continue; } let text_glyphs = &text_layout_info.glyphs; - let alignment_offset = (uinode.size / -2.0).extend(0.0); + let alignment_offset = (uinode.size() / -2.0).extend(0.0); let mut color = Color::WHITE; let mut current_section = usize::MAX; diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 2054a17d4850f..e469d2e30d107 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -14,14 +14,14 @@ fn main() { } fn setup(mut commands: Commands) { - commands.spawn_bundle(Camera2dBundle::default()); + commands.spawn(Camera2dBundle::default()); // spawn the container with default z-index. // the default z-index value is `ZIndex::Local(0)`. // because this is a root UI node, using local or global values will do the same thing. commands - .spawn_bundle(NodeBundle { - color: Color::GRAY.into(), + .spawn(NodeBundle { + background_color: Color::GRAY.into(), style: Style { size: Size::new(Val::Px(180.0), Val::Px(100.0)), margin: UiRect::all(Val::Auto), @@ -31,8 +31,8 @@ fn setup(mut commands: Commands) { }) .with_children(|parent| { // spawn a node with default z-index. - parent.spawn_bundle(NodeBundle { - color: Color::RED.into(), + parent.spawn(NodeBundle { + background_color: Color::RED.into(), style: Style { position_type: PositionType::Absolute, position: UiRect { @@ -48,9 +48,9 @@ fn setup(mut commands: Commands) { // spawn a node with a positive local z-index of 2. // it will show above other nodes in the grey container. - parent.spawn_bundle(NodeBundle { + parent.spawn(NodeBundle { z_index: ZIndex::Local(2), - color: Color::BLUE.into(), + background_color: Color::BLUE.into(), style: Style { position_type: PositionType::Absolute, position: UiRect { @@ -66,9 +66,9 @@ fn setup(mut commands: Commands) { // spawn a node with a negative local z-index. // it will show under other nodes in the grey container. - parent.spawn_bundle(NodeBundle { + parent.spawn(NodeBundle { z_index: ZIndex::Local(-1), - color: Color::GREEN.into(), + background_color: Color::GREEN.into(), style: Style { position_type: PositionType::Absolute, position: UiRect { @@ -85,9 +85,9 @@ fn setup(mut commands: Commands) { // spawn a node with a positive global z-index of 1. // it will show above all other nodes, because it's the highest global z-index in this example. // by default, boxes all share the global z-index of 0 that the grey container is added to. - parent.spawn_bundle(NodeBundle { + parent.spawn(NodeBundle { z_index: ZIndex::Global(1), - color: Color::PURPLE.into(), + background_color: Color::PURPLE.into(), style: Style { position_type: PositionType::Absolute, position: UiRect { @@ -104,9 +104,9 @@ fn setup(mut commands: Commands) { // spawn a node with a negative global z-index of -1. // this will show under all other nodes including its parent, because it's the lowest global z-index // in this example. - parent.spawn_bundle(NodeBundle { + parent.spawn(NodeBundle { z_index: ZIndex::Global(-1), - color: Color::YELLOW.into(), + background_color: Color::YELLOW.into(), style: Style { position_type: PositionType::Absolute, position: UiRect {