Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ ui = [
"scene",
"audio",
"picking",
"bevy_ui_debug"
]

# COLLECTION: Enable this feature during development to improve the development experience. This adds features like asset hot-reloading and debugging tools. This should not be enabled for published apps!
Expand Down Expand Up @@ -284,6 +285,9 @@ ui_picking = ["bevy_internal/ui_picking"]
# Provides a debug overlay for bevy UI
bevy_ui_debug = ["bevy_internal/bevy_ui_debug"]

# Provides a free Transform transform for bevy UI
bevy_ui_contain = ["bevy_internal/bevy_ui_contain"]

# Force dynamic linking, which improves iterative compile times
dynamic_linking = ["dep:bevy_dylib", "bevy_internal/dynamic_linking"]

Expand Down Expand Up @@ -3981,6 +3985,11 @@ name = "ui_material"
path = "examples/ui/ui_material.rs"
doc-scrape-examples = true

[[example]]
name = "ui_contain"
path = "examples/ui/ui_contain.rs"
doc-scrape-examples = true

[package.metadata.example.ui_material]
name = "UI Material"
description = "Demonstrates creating and using custom Ui materials"
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ ui_picking = ["bevy_picking", "bevy_ui?/bevy_picking"]
# Provides a UI debug overlay
bevy_ui_debug = ["bevy_ui_render?/bevy_ui_debug"]

# Provides a free Transform transform for bevy UI
bevy_ui_contain = ["bevy_ui?/bevy_ui_contain", "bevy_ui_render/bevy_ui_contain"]

# Enable built in global state machines
bevy_state = ["dep:bevy_state"]

Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ bevy_picking = ["dep:bevy_picking", "dep:uuid"]

# Experimental features
ghost_nodes = []
bevy_ui_contain = []

[lints]
workspace = true
Expand Down
65 changes: 58 additions & 7 deletions crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "bevy_ui_contain")]
use crate::UiContainTarget;
use crate::{
experimental::{UiChildren, UiRootNodes},
ui_transform::{UiGlobalTransform, UiTransform},
Expand Down Expand Up @@ -96,10 +98,16 @@ pub fn ui_layout_system(
mut removed_children: RemovedComponents<Children>,
mut removed_content_sizes: RemovedComponents<ContentSize>,
mut removed_nodes: RemovedComponents<Node>,
#[cfg(feature = "bevy_ui_contain")] mut ui_contain_target_query: Query<&UiContainTarget>,
#[cfg(feature = "bevy_ui_contain")] mut ui_surface_query: Query<&mut UiSurface>,
) {
// When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node.
for entity in removed_content_sizes.read() {
ui_surface.try_remove_node_context(entity);
#[cfg(feature = "bevy_ui_contain")]
ui_surface_query.iter_mut().for_each(|mut ui_surface| {
ui_surface.try_remove_node_context(entity);
});
}

// Sync Node and ContentSize to Taffy for all nodes
Expand All @@ -117,21 +125,55 @@ pub fn ui_layout_system(
computed_target.physical_size.as_vec2(),
);
let measure = content_size.and_then(|mut c| c.measure.take());
#[cfg(feature = "bevy_ui_contain")]
if let Ok(target) = ui_contain_target_query.get_mut(entity) {
let Ok(mut ui_surface) = ui_surface_query.get_mut(target.0) else {
tracing::error!(
"hasn't UiSurface about UiContainTarget entity: {:?}",
entity
);
return;
};
ui_surface.upsert_node(&layout_context, entity, &node, measure);
} else {
ui_surface.upsert_node(&layout_context, entity, &node, measure);
}
#[cfg(not(feature = "bevy_ui_contain"))]
ui_surface.upsert_node(&layout_context, entity, &node, measure);
}
});

// update and remove children
for entity in removed_children.read() {
ui_surface.try_remove_children(entity);
#[cfg(feature = "bevy_ui_contain")]
ui_surface_query.iter_mut().for_each(|mut ui_surface| {
ui_surface.try_remove_children(entity);
});
}
#[cfg(feature = "bevy_ui_contain")]
ui_surface_query.iter_mut().for_each(|mut ui_surface| {
ui_surface.remove_entities(
removed_nodes
.read()
.filter(|entity| !node_query.contains(*entity)),
);
});

// clean up removed nodes after syncing children to avoid potential panic (invalid SlotMap key used)
ui_surface.remove_entities(
removed_nodes
.read()
.filter(|entity| !node_query.contains(*entity)),
);
#[cfg(feature = "bevy_ui_contain")]
ui_surface_query.iter_mut().for_each(|mut ui_surface| {
ui_surface.remove_entities(
removed_nodes
.read()
.filter(|entity| !node_query.contains(*entity)),
);
});

for ui_root_entity in ui_root_node_query.iter() {
fn update_children_recursively(
Expand All @@ -155,12 +197,21 @@ pub fn ui_layout_system(
}
}

update_children_recursively(
&mut ui_surface,
&ui_children,
&added_node_query,
ui_root_entity,
);
// if ui_root_entity has UiContainTarget,then select UiSurface of it first.
#[cfg(feature = "bevy_ui_contain")]
let ui_surface = if let Ok(target) = ui_contain_target_query.get(ui_root_entity) {
// if ui_root_entity has UiContainTarget,then select UiSurface of it first.
let Ok(ui_surface) = ui_surface_query.get_mut(target.0) else {
continue;
};
ui_surface.into_inner()
} else {
ui_surface.as_mut()
};
#[cfg(not(feature = "bevy_ui_contain"))]
let ui_surface = &mut ui_surface;

update_children_recursively(ui_surface, &ui_children, &added_node_query, ui_root_entity);

let (_, _, _, computed_target) = node_query.get(ui_root_entity).unwrap();

Expand All @@ -173,7 +224,7 @@ pub fn ui_layout_system(

update_uinode_geometry_recursive(
ui_root_entity,
&mut ui_surface,
ui_surface,
true,
computed_target.physical_size().as_vec2(),
Affine2::IDENTITY,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ui/src/layout/ui_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bevy_platform::collections::hash_map::Entry;
use taffy::TaffyTree;

use bevy_ecs::{
component::Component,
entity::{Entity, EntityHashMap},
prelude::Resource,
};
Expand All @@ -30,7 +31,7 @@ impl From<taffy::NodeId> for LayoutNode {
}
}

#[derive(Resource)]
#[derive(Resource, Component)]
pub struct UiSurface {
pub root_entity_to_viewport_node: EntityHashMap<taffy::NodeId>,
pub(super) entity_to_taffy: EntityHashMap<LayoutNode>,
Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ impl Plugin for UiPlugin {
ui_focus_system.in_set(UiSystems::Focus).after(InputSystems),
);

#[cfg(feature = "bevy_ui_contain")]
app.configure_sets(
PostUpdate,
PropagateSet::<UiContainTarget>::default().in_set(UiSystems::Propagate),
)
.add_plugins(HierarchyPropagatePlugin::<UiContainTarget>::new(PostUpdate));

#[cfg(feature = "bevy_picking")]
app.add_plugins(picking_backend::UiPickingPlugin)
.add_systems(
Expand Down
24 changes: 24 additions & 0 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bevy_ecs::{prelude::*, system::SystemParam};
use bevy_math::{vec4, Rect, UVec2, Vec2, Vec4Swizzles};
use bevy_reflect::prelude::*;
use bevy_sprite::BorderRect;
use bevy_transform::components::Transform;
use bevy_utils::once;
use bevy_window::{PrimaryWindow, WindowRef};
use core::{f32, num::NonZero};
Expand Down Expand Up @@ -2860,6 +2861,29 @@ impl ComputedUiRenderTargetInfo {
}
}

/// Pointing to [`UiContainSet`](crate::UiContainSet), actually choosing [`UiSurface`](crate::ui_surface::UiSurface) for layout.
/// This will determine whether the Ui is based on the camera's layout or the layout where `UiContainSet` is located in world space.
/// When the root node and its child nodes point to the same `UiContainSet`, the functionality is work.
/// You can use [`Propagate`](bevy_app::Propagate) to pass it to all child nodes.
#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]
#[reflect(Component, PartialEq, Clone)]
#[relationship(relationship_target = UiContains)]
pub struct UiContainTarget(pub Entity);

#[derive(Component, Default, Debug, PartialEq, Eq)]
#[relationship_target(relationship = UiContainTarget, linked_spawn)]
pub struct UiContains(Vec<Entity>);

#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]
#[reflect(Component, PartialEq, Clone)]
#[require(crate::ui_surface::UiSurface, Transform, UiContains)]
pub struct UiContainSet {
/// The scale factor of the target contain's render target.
pub scale_factor: f32,
/// The size of the target contain's viewport in physical pixels.
pub physical_size: UVec2,
}

#[cfg(test)]
mod tests {
use crate::GridPlacement;
Expand Down
25 changes: 25 additions & 0 deletions crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use crate::{
CalculatedClip, ComputedUiRenderTargetInfo, ComputedUiTargetCamera, DefaultUiCamera, Display,
Node, OverflowAxis, OverrideClip, UiScale, UiTargetCamera,
};
#[cfg(feature = "bevy_ui_contain")]
use crate::{UiContainSet, UiContainTarget};

use super::ComputedNode;
use bevy_app::Propagate;
Expand Down Expand Up @@ -140,6 +142,8 @@ pub fn propagate_ui_target_cameras(
camera_query: Query<&Camera>,
target_camera_query: Query<&UiTargetCamera>,
ui_root_nodes: UiRootNodes,
#[cfg(feature = "bevy_ui_contain")] ui_contian_target_query: Query<&UiContainTarget>,
#[cfg(feature = "bevy_ui_contain")] ui_surface_query: Query<&UiContainSet>,
) {
let default_camera_entity = default_ui_camera.get();

Expand All @@ -151,10 +155,31 @@ pub fn propagate_ui_target_cameras(
.or(default_camera_entity)
.unwrap_or(Entity::PLACEHOLDER);

#[cfg(feature = "bevy_ui_contain")]
let (scale_factor, physical_size) =
if let Ok(target) = ui_contian_target_query.get(root_entity) {
ui_surface_query
.get(target.0)
.map(|ui_contain| (ui_contain.scale_factor, ui_contain.physical_size))
.unwrap_or((1., UVec2::ZERO))
} else {
camera_query
.get(camera)
.ok()
.map(|camera| {
(
camera.target_scaling_factor().unwrap_or(1.) * ui_scale.0,
camera.physical_viewport_size().unwrap_or(UVec2::ZERO),
)
})
.unwrap_or((1., UVec2::ZERO))
};

commands
.entity(root_entity)
.try_insert(Propagate(ComputedUiTargetCamera { camera }));

#[cfg(not(feature = "bevy_ui_contain"))]
let (scale_factor, physical_size) = camera_query
.get(camera)
.ok()
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ui_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
default = []
serialize = ["bevy_math/serialize", "bevy_platform/serialize"]
bevy_ui_debug = []
bevy_ui_contain = []

[lints]
workspace = true
Expand Down
38 changes: 37 additions & 1 deletion crates/bevy_ui_render/src/box_shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ pub fn queue_shadows(
continue;
};

let Ok(view) = camera_views.get(default_camera_view.0) else {
let Ok(view) = camera_views.get(default_camera_view.ui_camera) else {
continue;
};

Expand Down Expand Up @@ -353,6 +353,42 @@ pub fn queue_shadows(
index,
indexed: true,
});

#[cfg(feature = "bevy_ui_contain")]
{
let Ok(view) = camera_views.get(default_camera_view.ui_contain) else {
continue;
};

let Some(transparent_phase) =
transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};

let pipeline = pipelines.specialize(
&pipeline_cache,
&box_shadow_pipeline,
BoxShadowPipelineKey {
hdr: view.hdr,
samples: shadow_samples.copied().unwrap_or_default().0,
},
);

transparent_phase.add(TransparentUi {
draw_function,
pipeline,
entity: (entity, extracted_shadow.main_entity),
sort_key: FloatOrd(
extracted_shadow.stack_index as f32 + stack_z_offsets::BOX_SHADOW,
),

batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None,
index,
indexed: true,
});
}
}
}

Expand Down
Loading
Loading