Skip to content
Open
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
2 changes: 1 addition & 1 deletion crates/bevy_render/src/render_phase/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2032,7 +2032,7 @@ pub trait PhaseItem: Sized + Send + Sync + 'static {
/// The corresponding entity that will be drawn.
///
/// This is used to fetch the render data of the entity, required by the draw function,
/// from the render world .
/// from the render world.
fn entity(&self) -> Entity;

/// The main world entity represented by this `PhaseItem`.
Expand Down
43 changes: 33 additions & 10 deletions crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,15 +310,23 @@ pub fn ui_layout_system(
}

let content_size = Vec2::new(layout.content_size.width, layout.content_size.height);
node.bypass_change_detection().content_size = content_size;
if node.content_size != content_size {
node.content_size = content_size;
}

let taffy_rect_to_border_rect = |rect: taffy::Rect<f32>| BorderRect {
min_inset: Vec2::new(rect.left, rect.top),
max_inset: Vec2::new(rect.right, rect.bottom),
};

node.bypass_change_detection().border = taffy_rect_to_border_rect(layout.border);
node.bypass_change_detection().padding = taffy_rect_to_border_rect(layout.padding);
let new_border = taffy_rect_to_border_rect(layout.border);
if node.border != new_border {
node.border = new_border;
}
let new_padding = taffy_rect_to_border_rect(layout.padding);
if node.padding != new_padding {
node.padding = new_padding;
}

// Compute the node's new global transform
let mut local_transform = transform.compute_affine(
Expand All @@ -334,16 +342,19 @@ pub fn ui_layout_system(
}

// We don't trigger change detection for changes to border radius
node.bypass_change_detection().border_radius = style.border_radius.resolve(
// unless the border radius actually changed
let new_border_radius = style.border_radius.resolve(
inverse_target_scale_factor.recip(),
node.size,
target_size,
);
if node.border_radius != new_border_radius {
node.border_radius = new_border_radius;
}

if let Some(outline) = maybe_outline {
// don't trigger change detection when only outlines are changed
let node = node.bypass_change_detection();
node.outline_width = if style.display != Display::None {
// don't trigger change detection unless the outline actually changed
let new_outline_width = if style.display != Display::None {
outline
.width
.resolve(
Expand All @@ -357,7 +368,11 @@ pub fn ui_layout_system(
0.
};

node.outline_offset = outline
if node.outline_width != new_outline_width {
node.outline_width = new_outline_width;
}

let new_outline_offset = outline
.offset
.resolve(
inverse_target_scale_factor.recip(),
Expand All @@ -368,10 +383,16 @@ pub fn ui_layout_system(
// Clamp outline offsets to at least the length of the node's shorter side
// Negative offset outlines can be useful to create thing like in-set focus indicators
.max(-0.5 * node.size.min_element());
if node.outline_offset != new_outline_offset {
node.outline_offset = new_outline_offset;
}
}

node.bypass_change_detection().scrollbar_size =
let new_scrollbar_size =
Vec2::new(layout.scrollbar_size.width, layout.scrollbar_size.height);
if node.scrollbar_size != new_scrollbar_size {
node.scrollbar_size = new_scrollbar_size;
}

let scroll_position: Vec2 = maybe_scroll_position
.map(|scroll_pos| {
Expand All @@ -396,7 +417,9 @@ pub fn ui_layout_system(

let physical_scroll_position = clamped_scroll_position.floor();

node.bypass_change_detection().scroll_position = physical_scroll_position;
if node.scroll_position != physical_scroll_position {
node.scroll_position = physical_scroll_position;
}

for child_uinode in ui_children.iter_ui_children(entity) {
update_uinode_geometry_recursive(
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,12 @@ impl Plugin for UiPlugin {
// They run independently since `widget::image_node_system` will only ever observe
// its own ImageNode, and `widget::text_system` & `bevy_text::update_text2d_layout`
// will never modify a pre-existing `Image` asset.
widget::update_image_content_size_system
(
widget::update_image_content_size_system,
widget::update_texture_atlas_layout_components,
widget::mark_images_as_changed_if_their_assets_changed,
)
.chain()
.in_set(UiSystems::Content)
.in_set(AmbiguousWithText)
.in_set(AmbiguousWithUpdateText2dLayout),
Expand Down
49 changes: 37 additions & 12 deletions crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use bevy_app::Propagate;
use bevy_camera::Camera;
use bevy_ecs::{
entity::Entity,
query::Has,
hierarchy::ChildOf,
query::{Has, With, Without},
system::{Commands, Query, Res},
};
use bevy_math::{Rect, UVec2};
Expand Down Expand Up @@ -118,21 +119,36 @@ pub fn propagate_ui_target_cameras(
ui_scale: Res<UiScale>,
camera_query: Query<&Camera>,
target_camera_query: Query<&UiTargetCamera>,
ui_root_nodes: UiRootNodes,
ui_root_nodes: Query<
(
Entity,
Option<&Propagate<ComputedUiTargetCamera>>,
Option<&Propagate<ComputedUiRenderTargetInfo>>,
),
(With<Node>, Without<ChildOf>),
>,
) {
let default_camera_entity = default_ui_camera.get();

for root_entity in ui_root_nodes.iter() {
for (root_entity, maybe_computed_ui_target_camera, maybe_computed_ui_render_target_info) in
ui_root_nodes.iter()
{
let camera = target_camera_query
.get(root_entity)
.ok()
.map(UiTargetCamera::entity)
.or(default_camera_entity)
.unwrap_or(Entity::PLACEHOLDER);

commands
.entity(root_entity)
.try_insert(Propagate(ComputedUiTargetCamera { camera }));
// Only update `ComputedUiTargetCamera` if it actually changed.
match maybe_computed_ui_target_camera {
Some(computed_ui_target_camera) if computed_ui_target_camera.0.camera == camera => {}
_ => {
commands
.entity(root_entity)
.try_insert(Propagate(ComputedUiTargetCamera { camera }));
}
}

let (scale_factor, physical_size) = camera_query
.get(camera)
Expand All @@ -145,12 +161,21 @@ pub fn propagate_ui_target_cameras(
})
.unwrap_or((1., UVec2::ZERO));

commands
.entity(root_entity)
.try_insert(Propagate(ComputedUiRenderTargetInfo {
scale_factor,
physical_size,
}));
let new_computed_ui_render_target_info = ComputedUiRenderTargetInfo {
scale_factor,
physical_size,
};

// Only update `ComputedUiRenderTargetInfo` if it actually changed.
match maybe_computed_ui_render_target_info {
Some(computed_ui_render_target_info)
if computed_ui_render_target_info.0 == new_computed_ui_render_target_info => {}
_ => {
commands
.entity(root_entity)
.try_insert(Propagate(new_computed_ui_render_target_info));
}
}
}
}

Expand Down
70 changes: 69 additions & 1 deletion crates/bevy_ui/src/widget/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use crate::{
ComputedUiRenderTargetInfo, ContentSize, Measure, MeasureArgs, Node, NodeMeasure, ResolvedAxis,
VisualBox,
};
use bevy_asset::{AsAssetId, AssetId, Assets, Handle};
use bevy_asset::{asset_changed::AssetChanged, AsAssetId, AssetId, Assets, Handle};
use bevy_color::Color;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::*;
use bevy_image::{prelude::*, TRANSPARENT_IMAGE_HANDLE};
use bevy_math::{Rect, UVec2, Vec2};
Expand Down Expand Up @@ -256,6 +257,34 @@ impl ImageNodeSize {
}
}

// This component is more or less a workaround for the fact that `AsAssetId`
// only allows each component to expose one asset. `ImageNode` exposes two types
// of assets: an `Image` and a `TextureAtlasLayout`. We have to mark the image
// node as changed if either one of those assets changes. The only way to detect
// asset changes is to use the `AssetChanged` query filter. Unfortunately, the
// `AssetChanged` query filter relies on `AsAssetId`, which we can only
// implement once per component. Thus we need this second component, which
// essentially serves to provide a second implementation of `AsAssetId` on
// `ImageNode`.

/// The texture atlas layout, if the image has one.
///
/// The [`update_texture_atlas_layout_components`] system automatically keeps
/// this component up to date based on [`ImageNode::texture_atlas`]. Don't
/// update this component yourself; [`ImageNode::texture_atlas`] is the source
/// of truth.
#[derive(Component, Debug, Clone, Reflect, Deref, DerefMut)]
#[reflect(Component, Debug, Clone)]
pub struct ImageNodeTextureAtlasLayout(pub Handle<TextureAtlasLayout>);

impl AsAssetId for ImageNodeTextureAtlasLayout {
type Asset = TextureAtlasLayout;

fn as_asset_id(&self) -> AssetId<Self::Asset> {
self.id()
}
}

#[derive(Clone)]
/// Used to calculate the size of UI image nodes
pub struct ImageMeasure {
Expand Down Expand Up @@ -415,3 +444,42 @@ pub fn update_image_content_size_system(
}
}
}

/// A system that marks [`ImageNode`]s as changed if either their [`Image`] or
/// [`TextureAtlasLayout`] changed.
pub fn mark_images_as_changed_if_their_assets_changed(
mut images_query: Query<&mut ImageNode, AssetChanged<ImageNode>>,
mut texture_atlas_layouts_query: Query<
&mut ImageNodeTextureAtlasLayout,
AssetChanged<ImageNodeTextureAtlasLayout>,
>,
) {
for mut image in &mut images_query {
image.set_changed();
}
for mut texture_atlas_layout in &mut texture_atlas_layouts_query {
texture_atlas_layout.set_changed();
}
}

/// A system that copies the [`TextureAtlasLayout`] stored within an
/// [`ImageNode`] to the [`TextureAtlasLayout`] component.
pub fn update_texture_atlas_layout_components(
mut commands: Commands,
images_query: Query<(Entity, &ImageNode), Changed<ImageNode>>,
) {
for (entity, image_node) in &images_query {
match image_node.texture_atlas {
Some(ref texture_atlas) => {
commands
.entity(entity)
.insert(ImageNodeTextureAtlasLayout(texture_atlas.layout.clone()));
}
None => {
commands
.entity(entity)
.remove::<ImageNodeTextureAtlasLayout>();
}
}
}
}
Loading
Loading