From cdaae01c74be6988525a5d8cf0c99581d8cfb3c6 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 23 Jun 2023 13:42:17 +0100 Subject: [PATCH] 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) { 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 bb59509d44d29: image-size-broke * Result with this PR (and Bevy 0.10.1): image-size-fixed --- ## 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 --- crates/bevy_ui/src/widget/image.rs | 65 ++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index b96031029a..0e638c5495 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -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, + windows: Query<&Window, With>, + ui_scale: Res, textures: Res>, #[cfg(feature = "bevy_text")] mut query: Query< (&mut ContentSize, &UiImage, &mut UiImageSize), @@ -78,23 +88,37 @@ pub fn update_image_content_size_system( With, >, ) { + 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, + windows: Query<&Window, With>, + ui_scale: Res, atlases: Res>, #[cfg(feature = "bevy_text")] mut atlas_query: Query< ( @@ -115,18 +139,25 @@ pub fn update_atlas_content_size_system( (With, Without), >, ) { + 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; }