Apply scale factor to ImageMeasure sizes (#8545)

# Objective

In Bevy main, the unconstrained size of an `ImageBundle` or
`AtlasImageBundle` UI node is based solely on the size of its texture
and doesn't change with window scale factor or `UiScale`.

## Solution

* The size field of each `ImageMeasure` should be multiplied by the
current combined scale factor.
* Each `ImageMeasure` should be updated when the combined scale factor
is changed.

## Example:
```rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(UiScale { scale: 1.5 })
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2dBundle::default());
    commands.spawn(NodeBundle {
        style: Style {
            // The size of the "bevy_logo_dark.png" texture is 520x130 pixels
            width: Val::Px(520.),
            height: Val::Px(130.),
            ..Default::default()
        },
        background_color: Color::RED.into(),
        ..Default::default()
    });
    commands
        .spawn(ImageBundle {
            style: Style {
                position_type: PositionType::Absolute,
                ..Default::default()
            },
            image: UiImage::new(asset_server.load("bevy_logo_dark.png")),
            ..Default::default()
        });
}
```

The red node is given a size with the same dimensions as the texture. So
we would expect the texture to fill the node exactly.

* Result with Bevy main branch  bb59509d44:
<img width="400" alt="image-size-broke"
src="https://github.com/bevyengine/bevy/assets/27962798/19fd927d-ecc5-49a7-be05-c121a8df163f">

* Result with this PR (and Bevy 0.10.1):
<img width="400" alt="image-size-fixed"
src="https://github.com/bevyengine/bevy/assets/27962798/40b47820-5f2d-408f-88ef-9e2beb9c92a0">

---

## Changelog

`bevy_ui::widget::image`
* Update all `ImageMeasure`s on changes to the window scale factor or
`UiScale`.
* Multiply `ImageMeasure::size` by the window scale factor and
`UiScale`.

## Migration Guide
This commit is contained in:
ickshonpe 2023-06-23 13:42:17 +01:00 committed by GitHub
parent 29f7293e30
commit cdaae01c74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,14 +1,15 @@
use crate::{
measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiTextureAtlasImage,
measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale, UiTextureAtlasImage,
};
use bevy_asset::{Assets, Handle};
#[cfg(feature = "bevy_text")]
use bevy_ecs::query::Without;
use bevy_ecs::{
prelude::Component,
query::With,
reflect::ReflectComponent,
system::{Query, Res},
system::{Local, Query, Res},
};
use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect};
@ -16,26 +17,32 @@ use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
use bevy_text::Text;
use bevy_window::{PrimaryWindow, Window};
/// The size of the image in physical pixels
/// The size of the image's texture
///
/// This field is set automatically by `update_image_calculated_size_system`
/// This component is updated automatically by [`update_image_content_size_system`]
#[derive(Component, Debug, Copy, Clone, Default, Reflect, FromReflect)]
#[reflect(Component, Default, FromReflect)]
pub struct UiImageSize {
/// The size of the image's texture
///
/// This field is updated automatically by [`update_image_content_size_system`]
size: Vec2,
}
impl UiImageSize {
/// The size of the image's texture
pub fn size(&self) -> Vec2 {
self.size
}
}
#[derive(Clone)]
/// Used to calculate the size of UI image nodes
pub struct ImageMeasure {
// target size of the image
size: Vec2,
/// The size of the image's texture
pub size: Vec2,
}
impl Measure for ImageMeasure {
@ -68,6 +75,9 @@ impl Measure for ImageMeasure {
/// Updates content size of the node based on the image provided
pub fn update_image_content_size_system(
mut previous_combined_scale_factor: Local<f64>,
windows: Query<&Window, With<PrimaryWindow>>,
ui_scale: Res<UiScale>,
textures: Res<Assets<Image>>,
#[cfg(feature = "bevy_text")] mut query: Query<
(&mut ContentSize, &UiImage, &mut UiImageSize),
@ -78,23 +88,37 @@ pub fn update_image_content_size_system(
With<Node>,
>,
) {
let combined_scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.)
* ui_scale.scale;
for (mut content_size, image, mut image_size) in &mut query {
if let Some(texture) = textures.get(&image.texture) {
let size = Vec2::new(
texture.texture_descriptor.size.width as f32,
texture.texture_descriptor.size.height as f32,
);
// Update only if size has changed to avoid needless layout calculations
if size != image_size.size {
// Update only if size or scale factor has changed to avoid needless layout calculations
if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor {
image_size.size = size;
content_size.set(ImageMeasure { size });
content_size.set(ImageMeasure {
// multiply the image size by the scale factor to get the physical size
size: size * combined_scale_factor as f32,
});
}
}
}
*previous_combined_scale_factor = combined_scale_factor;
}
/// Updates content size of the node based on the texture atlas sprite
pub fn update_atlas_content_size_system(
mut previous_combined_scale_factor: Local<f64>,
windows: Query<&Window, With<PrimaryWindow>>,
ui_scale: Res<UiScale>,
atlases: Res<Assets<TextureAtlas>>,
#[cfg(feature = "bevy_text")] mut atlas_query: Query<
(
@ -115,18 +139,25 @@ pub fn update_atlas_content_size_system(
(With<Node>, Without<UiImage>),
>,
) {
let combined_scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.)
* ui_scale.scale;
for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query {
if let Some(atlas) = atlases.get(atlas) {
let texture_rect = atlas.textures[atlas_image.index];
let size = Vec2::new(
texture_rect.max.x - texture_rect.min.x,
texture_rect.max.y - texture_rect.min.y,
);
// Update only if size has changed to avoid needless layout calculations
if size != image_size.size {
let size = atlas.textures[atlas_image.index].size();
// Update only if size or scale factor has changed to avoid needless layout calculations
if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor {
image_size.size = size;
content_size.set(ImageMeasure { size });
content_size.set(ImageMeasure {
// multiply the image size by the scale factor to get the physical size
size: size * combined_scale_factor as f32,
});
}
}
}
*previous_combined_scale_factor = combined_scale_factor;
}