Remove ViewVisibility from UI nodes (#17405)

# Objective

The UI can only target a single view and doesn't support `RenderLayers`,
so there doesn't seem to be any need for UI nodes to require
`ViewVisibility` and `VisibilityClass`.

Fixes #17400

## Solution

Remove the `ViewVisibility` and `VisibilityClass` component requires
from `Node` and change the visibility queries to only query for
`InheritedVisibility`.

## Testing

```cargo run --example many_buttons --release --features "trace_tracy"```

Yellow is this PR, red is main.

`bevy_render::view::visibility::reset_view_visibility`
<img width="531" alt="reset-view" src="https://github.com/user-attachments/assets/a44b215d-96bf-43ec-8669-31530ff98eae" />

`bevy_render::view::visibility::check_visibility`
<img width="445" alt="view_visibility" src="https://github.com/user-attachments/assets/fa111757-da91-434d-88e4-80bdfa29374f" />
This commit is contained in:
ickshonpe 2025-01-23 05:26:10 +00:00 committed by GitHub
parent 68c19defb6
commit dd2d84b342
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 37 additions and 38 deletions

View File

@ -12,7 +12,7 @@ use bevy_ecs::{
use bevy_input::{mouse::MouseButton, touch::Touches, ButtonInput}; use bevy_input::{mouse::MouseButton, touch::Touches, ButtonInput};
use bevy_math::{Rect, Vec2}; use bevy_math::{Rect, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ViewVisibility}; use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::InheritedVisibility};
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use bevy_window::{PrimaryWindow, Window}; use bevy_window::{PrimaryWindow, Window};
@ -28,9 +28,9 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// ///
/// Updated in [`ui_focus_system`]. /// Updated in [`ui_focus_system`].
/// ///
/// If a UI node has both [`Interaction`] and [`ViewVisibility`] components, /// If a UI node has both [`Interaction`] and [`InheritedVisibility`] components,
/// [`Interaction`] will always be [`Interaction::None`] /// [`Interaction`] will always be [`Interaction::None`]
/// when [`ViewVisibility::get()`] is false. /// when [`InheritedVisibility::get()`] is false.
/// This ensures that hidden UI nodes are not interactable, /// This ensures that hidden UI nodes are not interactable,
/// and do not end up stuck in an active state if hidden at the wrong time. /// and do not end up stuck in an active state if hidden at the wrong time.
/// ///
@ -140,13 +140,13 @@ pub struct NodeQuery {
relative_cursor_position: Option<&'static mut RelativeCursorPosition>, relative_cursor_position: Option<&'static mut RelativeCursorPosition>,
focus_policy: Option<&'static FocusPolicy>, focus_policy: Option<&'static FocusPolicy>,
calculated_clip: Option<&'static CalculatedClip>, calculated_clip: Option<&'static CalculatedClip>,
view_visibility: Option<&'static ViewVisibility>, inherited_visibility: Option<&'static InheritedVisibility>,
target_camera: Option<&'static UiTargetCamera>, target_camera: Option<&'static UiTargetCamera>,
} }
/// The system that sets Interaction for all UI elements based on the mouse cursor activity /// The system that sets Interaction for all UI elements based on the mouse cursor activity
/// ///
/// Entities with a hidden [`ViewVisibility`] are always treated as released. /// Entities with a hidden [`InheritedVisibility`] are always treated as released.
pub fn ui_focus_system( pub fn ui_focus_system(
mut state: Local<State>, mut state: Local<State>,
camera_query: Query<(Entity, &Camera)>, camera_query: Query<(Entity, &Camera)>,
@ -227,9 +227,9 @@ pub fn ui_focus_system(
return None; return None;
}; };
let view_visibility = node.view_visibility?; let inherited_visibility = node.inherited_visibility?;
// Nodes that are not rendered should not be interactable // Nodes that are not rendered should not be interactable
if !view_visibility.get() { if !inherited_visibility.get() {
// Reset their interaction to None to avoid strange stuck state // Reset their interaction to None to avoid strange stuck state
if let Some(mut interaction) = node.interaction { if let Some(mut interaction) = node.interaction {
// We cannot simply set the interaction to None, as that will trigger change detection repeatedly // We cannot simply set the interaction to None, as that will trigger change detection repeatedly

View File

@ -50,7 +50,7 @@ pub struct NodeQuery {
global_transform: &'static GlobalTransform, global_transform: &'static GlobalTransform,
pickable: Option<&'static Pickable>, pickable: Option<&'static Pickable>,
calculated_clip: Option<&'static CalculatedClip>, calculated_clip: Option<&'static CalculatedClip>,
view_visibility: Option<&'static ViewVisibility>, inherited_visibility: Option<&'static InheritedVisibility>,
target_camera: Option<&'static UiTargetCamera>, target_camera: Option<&'static UiTargetCamera>,
} }
@ -124,8 +124,8 @@ pub fn ui_picking(
// Nodes that are not rendered should not be interactable // Nodes that are not rendered should not be interactable
if node if node
.view_visibility .inherited_visibility
.map(|view_visibility| view_visibility.get()) .map(|inherited_visibility| inherited_visibility.get())
!= Some(true) != Some(true)
{ {
continue; continue;

View File

@ -243,7 +243,7 @@ pub fn extract_shadows(
Entity, Entity,
&ComputedNode, &ComputedNode,
&GlobalTransform, &GlobalTransform,
&ViewVisibility, &InheritedVisibility,
&BoxShadow, &BoxShadow,
Option<&CalculatedClip>, Option<&CalculatedClip>,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,
@ -253,8 +253,7 @@ pub fn extract_shadows(
) { ) {
let default_camera_entity = default_ui_camera.get(); let default_camera_entity = default_ui_camera.get();
for (entity, uinode, transform, view_visibility, box_shadow, clip, camera) in &box_shadow_query for (entity, uinode, transform, visibility, box_shadow, clip, camera) in &box_shadow_query {
{
let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity) let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity)
else { else {
continue; continue;
@ -265,7 +264,7 @@ pub fn extract_shadows(
}; };
// Skip if no visible shadows // Skip if no visible shadows
if !view_visibility.get() || box_shadow.is_empty() || uinode.is_empty() { if !visibility.get() || box_shadow.is_empty() || uinode.is_empty() {
continue; continue;
} }

View File

@ -14,7 +14,7 @@ use bevy_math::Rect;
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_render::sync_world::RenderEntity; use bevy_render::sync_world::RenderEntity;
use bevy_render::sync_world::TemporaryRenderEntity; use bevy_render::sync_world::TemporaryRenderEntity;
use bevy_render::view::ViewVisibility; use bevy_render::view::InheritedVisibility;
use bevy_render::Extract; use bevy_render::Extract;
use bevy_sprite::BorderRect; use bevy_sprite::BorderRect;
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
@ -63,7 +63,7 @@ pub fn extract_debug_overlay(
Query<( Query<(
Entity, Entity,
&ComputedNode, &ComputedNode,
&ViewVisibility, &InheritedVisibility,
Option<&CalculatedClip>, Option<&CalculatedClip>,
&GlobalTransform, &GlobalTransform,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,

View File

@ -42,7 +42,7 @@ use bevy_render::{
render_phase::{PhaseItem, PhaseItemExtraIndex}, render_phase::{PhaseItem, PhaseItemExtraIndex},
sync_world::{RenderEntity, TemporaryRenderEntity}, sync_world::{RenderEntity, TemporaryRenderEntity},
texture::GpuImage, texture::GpuImage,
view::ViewVisibility, view::InheritedVisibility,
ExtractSchedule, Render, ExtractSchedule, Render,
}; };
use bevy_sprite::{BorderRect, SpriteAssetEvents}; use bevy_sprite::{BorderRect, SpriteAssetEvents};
@ -285,7 +285,7 @@ pub fn extract_uinode_background_colors(
Entity, Entity,
&ComputedNode, &ComputedNode,
&GlobalTransform, &GlobalTransform,
&ViewVisibility, &InheritedVisibility,
Option<&CalculatedClip>, Option<&CalculatedClip>,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,
&BackgroundColor, &BackgroundColor,
@ -294,7 +294,7 @@ pub fn extract_uinode_background_colors(
mapping: Extract<Query<RenderEntity>>, mapping: Extract<Query<RenderEntity>>,
) { ) {
let default_camera_entity = default_ui_camera.get(); let default_camera_entity = default_ui_camera.get();
for (entity, uinode, transform, view_visibility, clip, camera, background_color) in for (entity, uinode, transform, inherited_visibility, clip, camera, background_color) in
&uinode_query &uinode_query
{ {
let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity) let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity)
@ -307,7 +307,7 @@ pub fn extract_uinode_background_colors(
}; };
// Skip invisible backgrounds // Skip invisible backgrounds
if !view_visibility.get() || background_color.0.is_fully_transparent() { if !inherited_visibility.get() || background_color.0.is_fully_transparent() {
continue; continue;
} }
@ -348,7 +348,7 @@ pub fn extract_uinode_images(
Entity, Entity,
&ComputedNode, &ComputedNode,
&GlobalTransform, &GlobalTransform,
&ViewVisibility, &InheritedVisibility,
Option<&CalculatedClip>, Option<&CalculatedClip>,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,
&ImageNode, &ImageNode,
@ -357,7 +357,7 @@ pub fn extract_uinode_images(
mapping: Extract<Query<RenderEntity>>, mapping: Extract<Query<RenderEntity>>,
) { ) {
let default_camera_entity = default_ui_camera.get(); let default_camera_entity = default_ui_camera.get();
for (entity, uinode, transform, view_visibility, clip, camera, image) in &uinode_query { for (entity, uinode, transform, inherited_visibility, clip, camera, image) in &uinode_query {
let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity) let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity)
else { else {
continue; continue;
@ -368,7 +368,7 @@ pub fn extract_uinode_images(
}; };
// Skip invisible images // Skip invisible images
if !view_visibility.get() if !inherited_visibility.get()
|| image.color.is_fully_transparent() || image.color.is_fully_transparent()
|| image.image.id() == TRANSPARENT_IMAGE_HANDLE.id() || image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
|| image.image_mode.uses_slices() || image.image_mode.uses_slices()
@ -439,7 +439,7 @@ pub fn extract_uinode_borders(
&Node, &Node,
&ComputedNode, &ComputedNode,
&GlobalTransform, &GlobalTransform,
&ViewVisibility, &InheritedVisibility,
Option<&CalculatedClip>, Option<&CalculatedClip>,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,
AnyOf<(&BorderColor, &Outline)>, AnyOf<(&BorderColor, &Outline)>,
@ -454,7 +454,7 @@ pub fn extract_uinode_borders(
node, node,
computed_node, computed_node,
global_transform, global_transform,
view_visibility, inherited_visibility,
maybe_clip, maybe_clip,
maybe_camera, maybe_camera,
(maybe_border_color, maybe_outline), (maybe_border_color, maybe_outline),
@ -472,7 +472,7 @@ pub fn extract_uinode_borders(
}; };
// Skip invisible borders and removed nodes // Skip invisible borders and removed nodes
if !view_visibility.get() || node.display == Display::None { if !inherited_visibility.get() || node.display == Display::None {
continue; continue;
} }
@ -678,7 +678,7 @@ pub fn extract_text_sections(
Entity, Entity,
&ComputedNode, &ComputedNode,
&GlobalTransform, &GlobalTransform,
&ViewVisibility, &InheritedVisibility,
Option<&CalculatedClip>, Option<&CalculatedClip>,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,
&ComputedTextBlock, &ComputedTextBlock,
@ -696,7 +696,7 @@ pub fn extract_text_sections(
entity, entity,
uinode, uinode,
global_transform, global_transform,
view_visibility, inherited_visibility,
clip, clip,
camera, camera,
computed_block, computed_block,
@ -708,7 +708,7 @@ pub fn extract_text_sections(
}; };
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`) // Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
if !view_visibility.get() || uinode.is_empty() { if !inherited_visibility.get() || uinode.is_empty() {
continue; continue;
} }

View File

@ -369,7 +369,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
&ComputedNode, &ComputedNode,
&GlobalTransform, &GlobalTransform,
&MaterialNode<M>, &MaterialNode<M>,
&ViewVisibility, &InheritedVisibility,
Option<&CalculatedClip>, Option<&CalculatedClip>,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,
)>, )>,
@ -379,7 +379,9 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
// If there is only one camera, we use it as default // If there is only one camera, we use it as default
let default_single_camera = default_ui_camera.get(); let default_single_camera = default_ui_camera.get();
for (entity, uinode, transform, handle, view_visibility, clip, camera) in uinode_query.iter() { for (entity, uinode, transform, handle, inherited_visibility, clip, camera) in
uinode_query.iter()
{
let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_single_camera) let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_single_camera)
else { else {
continue; continue;
@ -390,7 +392,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
}; };
// skip invisible nodes // skip invisible nodes
if !view_visibility.get() { if !inherited_visibility.get() {
continue; continue;
} }

View File

@ -254,7 +254,7 @@ pub fn extract_ui_texture_slices(
Entity, Entity,
&ComputedNode, &ComputedNode,
&GlobalTransform, &GlobalTransform,
&ViewVisibility, &InheritedVisibility,
Option<&CalculatedClip>, Option<&CalculatedClip>,
Option<&UiTargetCamera>, Option<&UiTargetCamera>,
&ImageNode, &ImageNode,
@ -264,7 +264,7 @@ pub fn extract_ui_texture_slices(
) { ) {
let default_camera_entity = default_ui_camera.get(); let default_camera_entity = default_ui_camera.get();
for (entity, uinode, transform, view_visibility, clip, camera, image) in &slicers_query { for (entity, uinode, transform, inherited_visibility, clip, camera, image) in &slicers_query {
let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity) let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_camera_entity)
else { else {
continue; continue;
@ -291,7 +291,7 @@ pub fn extract_ui_texture_slices(
}; };
// Skip invisible images // Skip invisible images
if !view_visibility.get() if !inherited_visibility.get()
|| image.color.is_fully_transparent() || image.color.is_fully_transparent()
|| image.image.id() == TRANSPARENT_IMAGE_HANDLE.id() || image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
{ {

View File

@ -6,7 +6,7 @@ use bevy_math::{vec4, Rect, Vec2, Vec4Swizzles};
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
use bevy_render::{ use bevy_render::{
camera::{Camera, RenderTarget}, camera::{Camera, RenderTarget},
view::{self, Visibility, VisibilityClass}, view::Visibility,
}; };
use bevy_sprite::BorderRect; use bevy_sprite::BorderRect;
use bevy_transform::components::Transform; use bevy_transform::components::Transform;
@ -329,11 +329,9 @@ impl From<Vec2> for ScrollPosition {
ScrollPosition, ScrollPosition,
Transform, Transform,
Visibility, Visibility,
VisibilityClass,
ZIndex ZIndex
)] )]
#[reflect(Component, Default, PartialEq, Debug)] #[reflect(Component, Default, PartialEq, Debug)]
#[component(on_add = view::add_visibility_class::<Node>)]
#[cfg_attr( #[cfg_attr(
feature = "serialize", feature = "serialize",
derive(serde::Serialize, serde::Deserialize), derive(serde::Serialize, serde::Deserialize),